Rename ngl to gl
authorMatthias Clasen <mclasen@redhat.com>
Thu, 7 Oct 2021 03:15:25 +0000 (23:15 -0400)
committerMatthias Clasen <mclasen@redhat.com>
Thu, 7 Oct 2021 17:05:53 +0000 (13:05 -0400)
We have only one gl renderer now, and it is
a bit odd for it not be called gl.

133 files changed:
demos/node-editor/node-editor-window.c
gdk/gdkmemoryformat.c
gdk/gdkmemorytexture.c
gdk/loaders/gdkpng.c
gsk/gl/fp16.c [new file with mode: 0644]
gsk/gl/fp16i.c [new file with mode: 0644]
gsk/gl/fp16private.h [new file with mode: 0644]
gsk/gl/gskglattachmentstate.c [new file with mode: 0644]
gsk/gl/gskglattachmentstateprivate.h [new file with mode: 0644]
gsk/gl/gskglbuffer.c [new file with mode: 0644]
gsk/gl/gskglbufferprivate.h [new file with mode: 0644]
gsk/gl/gskglcommandqueue.c [new file with mode: 0644]
gsk/gl/gskglcommandqueueprivate.h [new file with mode: 0644]
gsk/gl/gskglcompiler.c [new file with mode: 0644]
gsk/gl/gskglcompilerprivate.h [new file with mode: 0644]
gsk/gl/gskgldriver.c [new file with mode: 0644]
gsk/gl/gskgldriverprivate.h [new file with mode: 0644]
gsk/gl/gskglglyphlibrary.c [new file with mode: 0644]
gsk/gl/gskglglyphlibraryprivate.h [new file with mode: 0644]
gsk/gl/gskgliconlibrary.c [new file with mode: 0644]
gsk/gl/gskgliconlibraryprivate.h [new file with mode: 0644]
gsk/gl/gskglprofiler.c [new file with mode: 0644]
gsk/gl/gskglprofilerprivate.h [new file with mode: 0644]
gsk/gl/gskglprogram.c [new file with mode: 0644]
gsk/gl/gskglprogramprivate.h [new file with mode: 0644]
gsk/gl/gskglprograms.defs [new file with mode: 0644]
gsk/gl/gskglrenderer.c [new file with mode: 0644]
gsk/gl/gskglrenderer.h [new file with mode: 0644]
gsk/gl/gskglrendererprivate.h [new file with mode: 0644]
gsk/gl/gskglrenderjob.c [new file with mode: 0644]
gsk/gl/gskglrenderjobprivate.h [new file with mode: 0644]
gsk/gl/gskglshadowlibrary.c [new file with mode: 0644]
gsk/gl/gskglshadowlibraryprivate.h [new file with mode: 0644]
gsk/gl/gskgltexture.c [new file with mode: 0644]
gsk/gl/gskgltexturelibrary.c [new file with mode: 0644]
gsk/gl/gskgltexturelibraryprivate.h [new file with mode: 0644]
gsk/gl/gskgltextureprivate.h [new file with mode: 0644]
gsk/gl/gskgltypesprivate.h [new file with mode: 0644]
gsk/gl/gskgluniformstate.c [new file with mode: 0644]
gsk/gl/gskgluniformstateprivate.h [new file with mode: 0644]
gsk/gl/inlinearray.h [new file with mode: 0644]
gsk/gl/ninesliceprivate.h [new file with mode: 0644]
gsk/gl/resources/blend.glsl [new file with mode: 0644]
gsk/gl/resources/blit.glsl [new file with mode: 0644]
gsk/gl/resources/blur.glsl [new file with mode: 0644]
gsk/gl/resources/border.glsl [new file with mode: 0644]
gsk/gl/resources/color.glsl [new file with mode: 0644]
gsk/gl/resources/color_matrix.glsl [new file with mode: 0644]
gsk/gl/resources/coloring.glsl [new file with mode: 0644]
gsk/gl/resources/conic_gradient.glsl [new file with mode: 0644]
gsk/gl/resources/cross_fade.glsl [new file with mode: 0644]
gsk/gl/resources/custom.glsl [new file with mode: 0644]
gsk/gl/resources/filled_border.glsl [new file with mode: 0644]
gsk/gl/resources/inset_shadow.glsl [new file with mode: 0644]
gsk/gl/resources/linear_gradient.glsl [new file with mode: 0644]
gsk/gl/resources/outset_shadow.glsl [new file with mode: 0644]
gsk/gl/resources/preamble.fs.glsl [new file with mode: 0644]
gsk/gl/resources/preamble.glsl [new file with mode: 0644]
gsk/gl/resources/preamble.vs.glsl [new file with mode: 0644]
gsk/gl/resources/radial_gradient.glsl [new file with mode: 0644]
gsk/gl/resources/repeat.glsl [new file with mode: 0644]
gsk/gl/resources/unblurred_outset_shadow.glsl [new file with mode: 0644]
gsk/gl/stb_rect_pack.c [new file with mode: 0644]
gsk/gl/stb_rect_pack.h [new file with mode: 0644]
gsk/gskglshader.c
gsk/gskrenderer.c
gsk/meson.build
gsk/ngl/fp16.c [deleted file]
gsk/ngl/fp16i.c [deleted file]
gsk/ngl/fp16private.h [deleted file]
gsk/ngl/gskglprofiler.c [deleted file]
gsk/ngl/gskglprofilerprivate.h [deleted file]
gsk/ngl/gsknglattachmentstate.c [deleted file]
gsk/ngl/gsknglattachmentstateprivate.h [deleted file]
gsk/ngl/gsknglbuffer.c [deleted file]
gsk/ngl/gsknglbufferprivate.h [deleted file]
gsk/ngl/gsknglcommandqueue.c [deleted file]
gsk/ngl/gsknglcommandqueueprivate.h [deleted file]
gsk/ngl/gsknglcompiler.c [deleted file]
gsk/ngl/gsknglcompilerprivate.h [deleted file]
gsk/ngl/gskngldriver.c [deleted file]
gsk/ngl/gskngldriverprivate.h [deleted file]
gsk/ngl/gsknglglyphlibrary.c [deleted file]
gsk/ngl/gsknglglyphlibraryprivate.h [deleted file]
gsk/ngl/gskngliconlibrary.c [deleted file]
gsk/ngl/gskngliconlibraryprivate.h [deleted file]
gsk/ngl/gsknglprogram.c [deleted file]
gsk/ngl/gsknglprogramprivate.h [deleted file]
gsk/ngl/gsknglprograms.defs [deleted file]
gsk/ngl/gsknglrenderer.c [deleted file]
gsk/ngl/gsknglrenderer.h [deleted file]
gsk/ngl/gsknglrendererprivate.h [deleted file]
gsk/ngl/gsknglrenderjob.c [deleted file]
gsk/ngl/gsknglrenderjobprivate.h [deleted file]
gsk/ngl/gsknglshadowlibrary.c [deleted file]
gsk/ngl/gsknglshadowlibraryprivate.h [deleted file]
gsk/ngl/gskngltexture.c [deleted file]
gsk/ngl/gskngltexturelibrary.c [deleted file]
gsk/ngl/gskngltexturelibraryprivate.h [deleted file]
gsk/ngl/gskngltextureprivate.h [deleted file]
gsk/ngl/gskngltypesprivate.h [deleted file]
gsk/ngl/gskngluniformstate.c [deleted file]
gsk/ngl/gskngluniformstateprivate.h [deleted file]
gsk/ngl/inlinearray.h [deleted file]
gsk/ngl/ninesliceprivate.h [deleted file]
gsk/ngl/resources/blend.glsl [deleted file]
gsk/ngl/resources/blit.glsl [deleted file]
gsk/ngl/resources/blur.glsl [deleted file]
gsk/ngl/resources/border.glsl [deleted file]
gsk/ngl/resources/color.glsl [deleted file]
gsk/ngl/resources/color_matrix.glsl [deleted file]
gsk/ngl/resources/coloring.glsl [deleted file]
gsk/ngl/resources/conic_gradient.glsl [deleted file]
gsk/ngl/resources/cross_fade.glsl [deleted file]
gsk/ngl/resources/custom.glsl [deleted file]
gsk/ngl/resources/filled_border.glsl [deleted file]
gsk/ngl/resources/inset_shadow.glsl [deleted file]
gsk/ngl/resources/linear_gradient.glsl [deleted file]
gsk/ngl/resources/outset_shadow.glsl [deleted file]
gsk/ngl/resources/preamble.fs.glsl [deleted file]
gsk/ngl/resources/preamble.glsl [deleted file]
gsk/ngl/resources/preamble.vs.glsl [deleted file]
gsk/ngl/resources/radial_gradient.glsl [deleted file]
gsk/ngl/resources/repeat.glsl [deleted file]
gsk/ngl/resources/unblurred_outset_shadow.glsl [deleted file]
gsk/ngl/stb_rect_pack.c [deleted file]
gsk/ngl/stb_rect_pack.h [deleted file]
gtk/gtktestutils.c
gtk/inspector/general.c
testsuite/gdk/memorytexture.c
testsuite/gdk/texture-threads.c
testsuite/gsk/half-float.c
testsuite/gsk/meson.build

index 866b51c2848a71f647aa54a702b2beea2d30e872..4a22a0b9d85bbb92ec92bb238db378aaf49e69d8 100644 (file)
@@ -24,7 +24,7 @@
 #include "gtkrendererpaintableprivate.h"
 
 #include "gsk/gskrendernodeparserprivate.h"
-#include "gsk/ngl/gsknglrenderer.h"
+#include "gsk/gl/gskglrenderer.h"
 #ifdef GDK_WINDOWING_BROADWAY
 #include "gsk/broadway/gskbroadwayrenderer.h"
 #endif
@@ -872,7 +872,7 @@ node_editor_window_realize (GtkWidget *widget)
                                    "Default");
 #endif
   node_editor_window_add_renderer (self,
-                                   gsk_ngl_renderer_new (),
+                                   gsk_gl_renderer_new (),
                                    "OpenGL");
 #ifdef GDK_RENDERING_VULKAN
   node_editor_window_add_renderer (self,
index 5af2bb8516d07221b38305ce8b5690e2a505bfdc..abbe8c03f7810e3b1f1123e0a6da21d9df1513d5 100644 (file)
@@ -21,7 +21,7 @@
 
 #include "gdkmemoryformatprivate.h"
 
-#include "gsk/ngl/fp16private.h"
+#include "gsk/gl/fp16private.h"
 
 #include <epoxy/gl.h>
 
index 52723ef80500d872672bd8508903387f785544fd..e658ad97ee514c54aab6f30c74e0201791c002c7 100644 (file)
@@ -22,7 +22,7 @@
 #include "gdkmemorytextureprivate.h"
 
 #include "gdkmemoryformatprivate.h"
-#include "gsk/ngl/fp16private.h"
+#include "gsk/gl/fp16private.h"
 
 /**
  * GdkMemoryTexture:
index 9bdc5e3a43821063ec48d438cd654a3e038f1ce6..69f85eaa985fabbd7cbeec8b7cb27a6cdd1a5dce 100644 (file)
@@ -25,7 +25,7 @@
 #include "gdkprofilerprivate.h"
 #include "gdktexture.h"
 #include "gdktextureprivate.h"
-#include "gsk/ngl/fp16private.h"
+#include "gsk/gl/fp16private.h"
 #include <png.h>
 #include <stdio.h>
 
diff --git a/gsk/gl/fp16.c b/gsk/gl/fp16.c
new file mode 100644 (file)
index 0000000..29e8352
--- /dev/null
@@ -0,0 +1,254 @@
+/* fp16.c
+ *
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "fp16private.h"
+
+static inline guint
+as_uint (const float x)
+{
+  return *(guint*)&x;
+}
+
+static inline float
+as_float (const guint x)
+{
+  return *(float*)&x;
+}
+
+// IEEE-754 16-bit floating-point format (without infinity): 1-5-10
+
+static inline float
+half_to_float_one (const guint16 x)
+{
+  const guint e = (x&0x7C00)>>10; // exponent
+  const guint m = (x&0x03FF)<<13; // mantissa
+  const guint v = as_uint((float)m)>>23;
+  return as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000)));
+}
+
+static inline guint16
+float_to_half_one (const float x)
+{
+  const guint b = as_uint(x)+0x00001000; // round-to-nearest-even
+  const guint e = (b&0x7F800000)>>23; // exponent
+  const guint m = b&0x007FFFFF; // mantissa
+  return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF; // sign : normalized : denormalized : saturate
+}
+
+void
+float_to_half4_c (const float f[4],
+                  guint16     h[4])
+{
+  h[0] = float_to_half_one (f[0]);
+  h[1] = float_to_half_one (f[1]);
+  h[2] = float_to_half_one (f[2]);
+  h[3] = float_to_half_one (f[3]);
+}
+
+void
+half_to_float4_c (const guint16 h[4],
+                  float         f[4])
+{
+  f[0] = half_to_float_one (h[0]);
+  f[1] = half_to_float_one (h[1]);
+  f[2] = half_to_float_one (h[2]);
+  f[3] = half_to_float_one (h[3]);
+}
+
+void
+float_to_half_c (const float *f,
+                 guint16     *h,
+                 int          n)
+{
+  for (int i = 0; i < n; i++)
+    h[i] = float_to_half_one (f[i]);
+}
+
+void
+half_to_float_c (const guint16 *h,
+                 float         *f,
+                 int            n)
+{
+  for (int i = 0; i < n; i++)
+    f[i] = half_to_float_one (h[i]);
+}
+
+#ifdef HAVE_F16C
+
+#if defined(_MSC_VER) && !defined(__clang__)
+/* based on info from https://walbourn.github.io/directxmath-f16c-and-fma/ */
+static gboolean
+have_f16c_msvc (void)
+{
+  static gboolean result = FALSE;
+  static gsize inited = 0;
+
+  if (g_once_init_enter (&inited))
+    {
+      int cpuinfo[4] = { -1 };
+
+      __cpuid (cpuinfo, 0);
+
+      if (cpuinfo[0] > 0)
+        {
+          __cpuid (cpuinfo, 1);
+
+          if ((cpuinfo[2] & 0x8000000) != 0)
+            result = (cpuinfo[2] & 0x20000000) != 0;
+        }
+
+      g_once_init_leave (&inited, 1);
+    }
+
+  return result;
+}
+
+void
+float_to_half4 (const float f[4], guint16 h[4])
+{
+  if (have_f16c_msvc ())
+    float_to_half4_f16c (f, h);
+  else
+    float_to_half4_c (f, h);
+}
+
+void
+half_to_float4 (const guint16 h[4], float f[4])
+{
+  if (have_f16c_msvc ())
+    half_to_float4_f16c (h, f);
+  else
+    half_to_float4_c (h, f);
+}
+
+void
+float_to_half (const float *f, guint16 *h, int n)
+{
+  if (have_f16c_msvc ())
+    float_to_half_f16c (f, h, n);
+  else
+    float_to_half_c (f, h, n);
+}
+
+void
+half_to_float (const guint16 *h, float *f, int n)
+{
+  if (have_f16c_msvc ())
+    half_to_float_f16c (h, f, n);
+  else
+    half_to_float_c (h, f, n);
+}
+
+#else
+
+void float_to_half4 (const float f[4], guint16 h[4]) __attribute__((ifunc ("resolve_float_to_half4")));
+void half_to_float4 (const guint16 h[4], float f[4]) __attribute__((ifunc ("resolve_half_to_float4")));
+void float_to_half (const float *f, guint16 *h, int n) __attribute__((ifunc ("resolve_float_to_half")));
+void half_to_float (const guint16 *h, float *f, int n) __attribute__((ifunc ("resolve_half_to_float")));
+
+static void *
+resolve_float_to_half4 (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return float_to_half4_f16c;
+  else
+    return float_to_half4_c;
+}
+
+static void *
+resolve_half_to_float4 (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return half_to_float4_f16c;
+  else
+    return half_to_float4_c;
+}
+
+static void *
+resolve_float_to_half (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return float_to_half_f16c;
+  else
+    return float_to_half_c;
+}
+
+static void *
+resolve_half_to_float (void)
+{
+  __builtin_cpu_init ();
+  if (__builtin_cpu_supports ("f16c"))
+    return half_to_float_f16c;
+  else
+    return half_to_float_c;
+}
+
+#endif
+
+#else /* ! HAVE_F16C */
+
+#if defined(__APPLE__) || (defined(_MSC_VER) && !defined(__clang__))
+// turns out aliases don't work on Darwin nor Visual Studio
+
+void
+float_to_half4 (const float f[4],
+                guint16     h[4])
+{
+  float_to_half4_c (f, h);
+}
+
+void
+half_to_float4 (const guint16 h[4],
+                float         f[4])
+{
+  half_to_float4_c (h, f);
+}
+
+void
+float_to_half (const float *f,
+               guint16     *h,
+               int          n)
+{
+  float_to_half_c (f, h, n);
+}
+
+void
+half_to_float (const guint16 *h,
+               float         *f,
+               int            n)
+{
+  half_to_float_c (h, f, n);
+}
+
+#else
+
+void float_to_half4 (const float f[4], guint16 h[4]) __attribute__((alias ("float_to_half4_c")));
+void half_to_float4 (const guint16 h[4], float f[4]) __attribute__((alias ("half_to_float4_c")));
+void float_to_half (const float *f, guint16 *h, int n) __attribute__((alias ("float_to_half_c")));
+void half_to_float (const guint16 *h, float *f, int n) __attribute__((alias ("half_to_float_c")));
+
+#endif
+
+#endif  /* HAVE_F16C */
diff --git a/gsk/gl/fp16i.c b/gsk/gl/fp16i.c
new file mode 100644 (file)
index 0000000..aff3841
--- /dev/null
@@ -0,0 +1,122 @@
+/* fp16i.c
+ *
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "fp16private.h"
+
+#ifdef HAVE_F16C
+#include <immintrin.h>
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define CAST_M128I_P(a) (__m128i const *) a
+#else
+#define CAST_M128I_P(a) (__m128i_u const *) a
+#endif
+void
+float_to_half4_f16c (const float f[4],
+                     guint16     h[4])
+{
+  __m128 s = _mm_loadu_ps (f);
+  __m128i i = _mm_cvtps_ph (s, 0);
+  _mm_storel_epi64 ((__m128i*)h, i);
+}
+
+void
+half_to_float4_f16c (const guint16 h[4],
+                     float         f[4])
+{
+  __m128i i = _mm_loadl_epi64 (CAST_M128I_P (h));
+  __m128 s = _mm_cvtph_ps (i);
+
+  _mm_store_ps (f, s);
+}
+
+#define ALIGNED(p, n) (GPOINTER_TO_UINT(p) % n == 0)
+void
+float_to_half_f16c (const float *f,
+                    guint16     *h,
+                    int          n)
+{
+  __m128 s;
+  __m128i i;
+  int j;
+  const float *ff = f;
+  guint16 *hh = h;
+
+  for (j = 0; j < n; j++)
+    {
+      if (ALIGNED (ff, 16) && ALIGNED (hh, 16))
+        break;
+      ff++;
+      hh++;
+    }
+
+  float_to_half_c (f, h, j);
+
+  for (; j + 4 < n; j += 4)
+    {
+      s = _mm_loadu_ps (ff);
+      i = _mm_cvtps_ph (s, 0);
+      _mm_storel_epi64 ((__m128i*)hh, i);
+      ff += 4;
+      hh += 4;
+    }
+
+  if (j < n)
+    float_to_half_c (ff, hh, n - j);
+}
+
+void
+half_to_float_f16c (const guint16 *h,
+                    float         *f,
+                    int            n)
+{
+  __m128i i;
+  __m128 s;
+  int j;
+  const guint16 *hh = h;
+  float *ff = f;
+
+  for (j = 0; j < n; j++)
+    {
+      if (ALIGNED (ff, 16) && ALIGNED (hh, 16))
+        break;
+      ff++;
+      hh++;
+    }
+
+  half_to_float_c (h, f, j);
+
+  for (; j + 4 < n; j += 4)
+    {
+      i = _mm_loadl_epi64 (CAST_M128I_P (hh));
+      s = _mm_cvtph_ps (i);
+      _mm_store_ps (ff, s);
+      hh += 4;
+      ff += 4;
+    }
+
+  if (j < n)
+    half_to_float_c (hh, ff, n - j);
+}
+
+#endif  /* HAVE_F16C */
+
diff --git a/gsk/gl/fp16private.h b/gsk/gl/fp16private.h
new file mode 100644 (file)
index 0000000..fbb95cd
--- /dev/null
@@ -0,0 +1,76 @@
+/* fp16private.h
+ *
+ * Copyright 2021 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __FP16_PRIVATE_H__
+#define __FP16_PRIVATE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define FP16_ZERO ((guint16)0)
+#define FP16_ONE ((guint16)15360)
+#define FP16_MINUS_ONE ((guint16)48128)
+
+void float_to_half4 (const float f[4],
+                     guint16     h[4]);
+
+void half_to_float4 (const guint16 h[4],
+                     float         f[4]);
+
+void float_to_half (const float *f,
+                    guint16     *h,
+                    int          n);
+
+void half_to_float (const guint16 *h,
+                    float         *f,
+                    int            n);
+
+void float_to_half4_f16c (const float f[4],
+                          guint16     h[4]);
+
+void half_to_float4_f16c (const guint16 h[4],
+                          float         f[4]);
+
+void float_to_half_f16c (const float *f,
+                         guint16     *h,
+                         int          n);
+
+void half_to_float_f16c (const guint16 *h,
+                         float         *f,
+                         int            n);
+
+void float_to_half4_c (const float f[4],
+                       guint16     h[4]);
+
+void half_to_float4_c (const guint16 h[4],
+                       float         f[4]);
+
+void float_to_half_c (const float *f,
+                      guint16     *h,
+                      int          n);
+
+void half_to_float_c (const guint16 *h,
+                      float         *f,
+                      int            n);
+
+G_END_DECLS
+
+#endif
diff --git a/gsk/gl/gskglattachmentstate.c b/gsk/gl/gskglattachmentstate.c
new file mode 100644 (file)
index 0000000..b05cc97
--- /dev/null
@@ -0,0 +1,106 @@
+/* gskglattachmentstate.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gskglattachmentstateprivate.h"
+
+GskGLAttachmentState *
+gsk_gl_attachment_state_new (void)
+{
+  GskGLAttachmentState *self;
+
+  self = g_atomic_rc_box_new0 (GskGLAttachmentState);
+
+  self->fbo.changed = FALSE;
+  self->fbo.id = 0;
+  self->n_changed = 0;
+
+  /* Initialize textures, assume we are 2D by default since it
+   * doesn't really matter until we bind something other than
+   * GL_TEXTURE0 to it anyway.
+   */
+  for (guint i = 0; i < G_N_ELEMENTS (self->textures); i++)
+    {
+      self->textures[i].target = GL_TEXTURE_2D;
+      self->textures[i].texture = GL_TEXTURE0;
+      self->textures[i].id = 0;
+      self->textures[i].changed = FALSE;
+      self->textures[i].initial = TRUE;
+    }
+
+  return self;
+}
+
+GskGLAttachmentState *
+gsk_gl_attachment_state_ref (GskGLAttachmentState *self)
+{
+  return g_atomic_rc_box_acquire (self);
+}
+
+void
+gsk_gl_attachment_state_unref (GskGLAttachmentState *self)
+{
+  g_atomic_rc_box_release (self);
+}
+
+void
+gsk_gl_attachment_state_bind_texture (GskGLAttachmentState *self,
+                                      GLenum                target,
+                                      GLenum                texture,
+                                      guint                 id)
+{
+  GskGLBindTexture *attach;
+
+  g_assert (self != NULL);
+  g_assert (target == GL_TEXTURE_1D ||
+            target == GL_TEXTURE_2D ||
+            target == GL_TEXTURE_3D);
+  g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16);
+
+  attach = &self->textures[texture - GL_TEXTURE0];
+
+  if (attach->target != target || attach->texture != texture || attach->id != id)
+    {
+      attach->target = target;
+      attach->texture = texture;
+      attach->id = id;
+      attach->initial = FALSE;
+
+      if (attach->changed == FALSE)
+        {
+          attach->changed = TRUE;
+          self->n_changed++;
+        }
+    }
+}
+
+void
+gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self,
+                                          guint                 id)
+{
+  g_assert (self != NULL);
+
+  if (self->fbo.id != id)
+    {
+      self->fbo.id = id;
+      self->fbo.changed = TRUE;
+    }
+}
diff --git a/gsk/gl/gskglattachmentstateprivate.h b/gsk/gl/gskglattachmentstateprivate.h
new file mode 100644 (file)
index 0000000..4052fa2
--- /dev/null
@@ -0,0 +1,71 @@
+/* gskglattachmentstateprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_ATTACHMENT_STATE_PRIVATE_H__
+#define __GSK_GL_ATTACHMENT_STATE_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLAttachmentState GskGLAttachmentState;
+typedef struct _GskGLBindFramebuffer GskGLBindFramebuffer;
+typedef struct _GskGLBindTexture     GskGLBindTexture;
+
+struct _GskGLBindTexture
+{
+  guint changed : 1;
+  guint initial : 1;
+  GLenum target : 30;
+  GLenum texture;
+  guint id;
+};
+
+G_STATIC_ASSERT (sizeof (GskGLBindTexture) == 12);
+
+struct _GskGLBindFramebuffer
+{
+  guint changed : 1;
+  guint id : 31;
+};
+
+G_STATIC_ASSERT (sizeof (GskGLBindFramebuffer) == 4);
+
+struct _GskGLAttachmentState
+{
+  GskGLBindFramebuffer fbo;
+  /* Increase if shaders add more textures */
+  GskGLBindTexture textures[4];
+  guint n_changed;
+};
+
+GskGLAttachmentState *gsk_gl_attachment_state_new              (void);
+GskGLAttachmentState *gsk_gl_attachment_state_ref              (GskGLAttachmentState *self);
+void                  gsk_gl_attachment_state_unref            (GskGLAttachmentState *self);
+void                  gsk_gl_attachment_state_bind_texture     (GskGLAttachmentState *self,
+                                                                GLenum                 target,
+                                                                GLenum                 texture,
+                                                                guint                  id);
+void                  gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self,
+                                                                guint                  id);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_ATTACHMENT_STATE_PRIVATE_H__ */
diff --git a/gsk/gl/gskglbuffer.c b/gsk/gl/gskglbuffer.c
new file mode 100644 (file)
index 0000000..63aabd1
--- /dev/null
@@ -0,0 +1,69 @@
+/* gskglbufferprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gskglbufferprivate.h"
+
+/**
+ * gsk_gl_buffer_init:
+ * @target: the target buffer such as %GL_ARRAY_BUFFER or %GL_UNIFORM_BUFFER
+ * @element_size: the size of elements within the buffer
+ *
+ * Creates a new `GskGLBuffer` which can be used to deliver data to shaders
+ * within a GLSL program. You can use this to store vertices such as with
+ * %GL_ARRAY_BUFFER or uniform data with %GL_UNIFORM_BUFFER.
+ */
+void
+gsk_gl_buffer_init (GskGLBuffer *self,
+                    GLenum       target,
+                    guint        element_size)
+{
+  memset (self, 0, sizeof *self);
+
+  /* Default to 2 pages, power-of-two growth from there */
+  self->buffer_len = 4096 * 2;
+  self->buffer = g_malloc (self->buffer_len);
+  self->target = target;
+  self->element_size = element_size;
+}
+
+GLuint
+gsk_gl_buffer_submit (GskGLBuffer *buffer)
+{
+  GLuint id;
+
+  glGenBuffers (1, &id);
+  glBindBuffer (buffer->target, id);
+  glBufferData (buffer->target, buffer->buffer_pos, buffer->buffer, GL_STATIC_DRAW);
+
+  buffer->buffer_pos = 0;
+  buffer->count = 0;
+
+  return id;
+}
+
+void
+gsk_gl_buffer_destroy (GskGLBuffer *buffer)
+{
+  g_clear_pointer (&buffer->buffer, g_free);
+}
diff --git a/gsk/gl/gskglbufferprivate.h b/gsk/gl/gskglbufferprivate.h
new file mode 100644 (file)
index 0000000..3f78389
--- /dev/null
@@ -0,0 +1,82 @@
+/* gskglbufferprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_BUFFER_PRIVATE_H__
+#define __GSK_GL_BUFFER_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLBuffer
+{
+  guint8 *buffer;
+  gsize   buffer_pos;
+  gsize   buffer_len;
+  guint   count;
+  GLenum  target;
+  gsize   element_size;
+} GskGLBuffer;
+
+void   gsk_gl_buffer_init    (GskGLBuffer *self,
+                              GLenum       target,
+                              guint        element_size);
+void   gsk_gl_buffer_destroy (GskGLBuffer *buffer);
+GLuint gsk_gl_buffer_submit  (GskGLBuffer *buffer);
+
+static inline gpointer
+gsk_gl_buffer_advance (GskGLBuffer *buffer,
+                       guint         count)
+{
+  gpointer ret;
+  gsize to_alloc = count * buffer->element_size;
+
+  if G_UNLIKELY (buffer->buffer_pos + to_alloc > buffer->buffer_len)
+    {
+      while (buffer->buffer_pos + to_alloc > buffer->buffer_len)
+        buffer->buffer_len *= 2;
+      buffer->buffer = g_realloc (buffer->buffer, buffer->buffer_len);
+    }
+
+  ret = buffer->buffer + buffer->buffer_pos;
+
+  buffer->buffer_pos += to_alloc;
+  buffer->count += count;
+
+  return ret;
+}
+
+static inline void
+gsk_gl_buffer_retract (GskGLBuffer *buffer,
+                       guint        count)
+{
+  buffer->buffer_pos -= count * buffer->element_size;
+  buffer->count -= count;
+}
+
+static inline guint
+gsk_gl_buffer_get_offset (GskGLBuffer *buffer)
+{
+  return buffer->count;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_BUFFER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglcommandqueue.c b/gsk/gl/gskglcommandqueue.c
new file mode 100644 (file)
index 0000000..13256cb
--- /dev/null
@@ -0,0 +1,1433 @@
+/* gskglcommandqueue.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemoryformatprivate.h>
+#include <gdk/gdkmemorytextureprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskroundedrectprivate.h>
+
+#include "gskglattachmentstateprivate.h"
+#include "gskglbufferprivate.h"
+#include "gskglcommandqueueprivate.h"
+#include "gskgluniformstateprivate.h"
+
+#include "inlinearray.h"
+
+G_DEFINE_TYPE (GskGLCommandQueue, gsk_gl_command_queue, G_TYPE_OBJECT)
+
+G_GNUC_UNUSED static inline void
+print_uniform (GskGLUniformFormat format,
+               guint              array_count,
+               gconstpointer      valueptr)
+{
+  const union {
+    graphene_matrix_t matrix[0];
+    GskRoundedRect rounded_rect[0];
+    float fval[0];
+    int ival[0];
+    guint uval[0];
+  } *data = valueptr;
+
+  switch (format)
+    {
+    case GSK_GL_UNIFORM_FORMAT_1F:
+      g_printerr ("1f<%f>", data->fval[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_2F:
+      g_printerr ("2f<%f,%f>", data->fval[0], data->fval[1]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_3F:
+      g_printerr ("3f<%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_4F:
+      g_printerr ("4f<%f,%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2], data->fval[3]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_1I:
+    case GSK_GL_UNIFORM_FORMAT_TEXTURE:
+      g_printerr ("1i<%d>", data->ival[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_1UI:
+      g_printerr ("1ui<%u>", data->uval[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_COLOR: {
+      char *str = gdk_rgba_to_string (valueptr);
+      g_printerr ("%s", str);
+      g_free (str);
+      break;
+    }
+
+    case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: {
+      char *str = gsk_rounded_rect_to_string (valueptr);
+      g_printerr ("%s", str);
+      g_free (str);
+      break;
+    }
+
+    case GSK_GL_UNIFORM_FORMAT_MATRIX: {
+      float mat[16];
+      graphene_matrix_to_float (&data->matrix[0], mat);
+      g_printerr ("matrix<");
+      for (guint i = 0; i < G_N_ELEMENTS (mat)-1; i++)
+        g_printerr ("%f,", mat[i]);
+      g_printerr ("%f>", mat[G_N_ELEMENTS (mat)-1]);
+      break;
+    }
+
+    case GSK_GL_UNIFORM_FORMAT_1FV:
+    case GSK_GL_UNIFORM_FORMAT_2FV:
+    case GSK_GL_UNIFORM_FORMAT_3FV:
+    case GSK_GL_UNIFORM_FORMAT_4FV:
+      /* non-V variants are -4 from V variants */
+      format -= 4;
+      g_printerr ("[");
+      for (guint i = 0; i < array_count; i++)
+        {
+          print_uniform (format, 0, valueptr);
+          if (i + 1 != array_count)
+            g_printerr (",");
+          valueptr = ((guint8*)valueptr + gsk_gl_uniform_format_size (format));
+        }
+      g_printerr ("]");
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_2I:
+      g_printerr ("2i<%d,%d>", data->ival[0], data->ival[1]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_3I:
+      g_printerr ("3i<%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_4I:
+      g_printerr ("3i<%d,%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2], data->ival[3]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_LAST:
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+G_GNUC_UNUSED static inline void
+gsk_gl_command_queue_print_batch (GskGLCommandQueue       *self,
+                                  const GskGLCommandBatch *batch)
+{
+  static const char *command_kinds[] = { "Clear", "Draw", };
+  guint framebuffer_id;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (batch != NULL);
+
+  if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
+    framebuffer_id = batch->clear.framebuffer;
+  else if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW)
+    framebuffer_id = batch->draw.framebuffer;
+  else
+    return;
+
+  g_printerr ("Batch {\n");
+  g_printerr ("         Kind: %s\n", command_kinds[batch->any.kind]);
+  g_printerr ("     Viewport: %dx%d\n", batch->any.viewport.width, batch->any.viewport.height);
+  g_printerr ("  Framebuffer: %d\n", framebuffer_id);
+
+  if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW)
+    {
+      g_printerr ("      Program: %d\n", batch->any.program);
+      g_printerr ("     Vertices: %d\n", batch->draw.vbo_count);
+
+      for (guint i = 0; i < batch->draw.bind_count; i++)
+        {
+          const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i];
+          g_printerr ("      Bind[%d]: %u\n", bind->texture, bind->id);
+        }
+
+      for (guint i = 0; i < batch->draw.uniform_count; i++)
+        {
+          const GskGLCommandUniform *uniform = &self->batch_uniforms.items[batch->draw.uniform_offset + i];
+          g_printerr ("  Uniform[%02d]: ", uniform->location);
+          print_uniform (uniform->info.format,
+                         uniform->info.array_count,
+                         gsk_gl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset));
+          g_printerr ("\n");
+        }
+    }
+  else if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
+    {
+      g_printerr ("         Bits: 0x%x\n", batch->clear.bits);
+    }
+
+  g_printerr ("}\n");
+}
+
+G_GNUC_UNUSED static inline void
+gsk_gl_command_queue_capture_png (GskGLCommandQueue *self,
+                                  const char        *filename,
+                                  guint              width,
+                                  guint              height,
+                                  gboolean           flip_y)
+{
+  guint stride;
+  guint8 *data;
+  GBytes *bytes;
+  GdkTexture *texture;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (filename != NULL);
+
+  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+  data = g_malloc_n (height, stride);
+
+  glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data);
+
+  if (flip_y)
+    {
+      guint8 *flipped = g_malloc_n (height, stride);
+
+      for (guint i = 0; i < height; i++)
+        memcpy (flipped + (height * stride) - ((i + 1) * stride),
+                data + (stride * i),
+                stride);
+
+      g_free (data);
+      data = flipped;
+    }
+
+  bytes = g_bytes_new_take (data, height * stride);
+  texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
+  g_bytes_unref (bytes);
+
+  gdk_texture_save_to_png (texture, filename);
+  g_object_unref (texture);
+}
+
+static inline gboolean
+will_ignore_batch (GskGLCommandQueue *self)
+{
+  if G_LIKELY (self->batches.len < G_MAXINT16)
+    return FALSE;
+
+  if (!self->have_truncated)
+    {
+      self->have_truncated = TRUE;
+      g_critical ("GL command queue too large, truncating further batches.");
+    }
+
+  return TRUE;
+}
+
+static inline guint
+snapshot_attachments (const GskGLAttachmentState *state,
+                      GskGLCommandBinds          *array)
+{
+  GskGLCommandBind *bind = gsk_gl_command_binds_append_n (array, G_N_ELEMENTS (state->textures));
+  guint count = 0;
+
+  for (guint i = 0; i < G_N_ELEMENTS (state->textures); i++)
+    {
+      if (state->textures[i].id)
+        {
+          bind[count].id = state->textures[i].id;
+          bind[count].texture = state->textures[i].texture;
+          count++;
+        }
+    }
+
+  if (count != G_N_ELEMENTS (state->textures))
+    array->len -= G_N_ELEMENTS (state->textures) - count;
+
+  return count;
+}
+
+static inline guint
+snapshot_uniforms (GskGLUniformState    *state,
+                   GskGLUniformProgram  *program,
+                   GskGLCommandUniforms *array)
+{
+  GskGLCommandUniform *uniform = gsk_gl_command_uniforms_append_n (array, program->n_mappings);
+  guint count = 0;
+
+  for (guint i = 0; i < program->n_mappings; i++)
+    {
+      const GskGLUniformMapping *mapping = &program->mappings[i];
+
+      if (!mapping->info.initial && mapping->location > -1)
+        {
+          uniform[count].location = mapping->location;
+          uniform[count].info = mapping->info;
+          count++;
+        }
+    }
+
+  if (count != program->n_mappings)
+    array->len -= program->n_mappings - count;
+
+  return count;
+}
+
+static inline gboolean
+snapshots_equal (GskGLCommandQueue *self,
+                 GskGLCommandBatch *first,
+                 GskGLCommandBatch *second)
+{
+  if (first->draw.bind_count != second->draw.bind_count ||
+      first->draw.uniform_count != second->draw.uniform_count)
+    return FALSE;
+
+  for (guint i = 0; i < first->draw.bind_count; i++)
+    {
+      const GskGLCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i];
+      const GskGLCommandBind *sb = &self->batch_binds.items[second->draw.bind_offset+i];
+
+      if (fb->id != sb->id || fb->texture != sb->texture)
+        return FALSE;
+    }
+
+  for (guint i = 0; i < first->draw.uniform_count; i++)
+    {
+      const GskGLCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i];
+      const GskGLCommandUniform *su = &self->batch_uniforms.items[second->draw.uniform_offset+i];
+      gconstpointer fdata;
+      gconstpointer sdata;
+      gsize len;
+
+      /* Short circuit if we'd end up with the same memory */
+      if (fu->info.offset == su->info.offset)
+        continue;
+
+      if (fu->info.format != su->info.format ||
+          fu->info.array_count != su->info.array_count)
+        return FALSE;
+
+      fdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset);
+      sdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, su->info.offset);
+
+      switch (fu->info.format)
+        {
+        case GSK_GL_UNIFORM_FORMAT_1F:
+        case GSK_GL_UNIFORM_FORMAT_1FV:
+        case GSK_GL_UNIFORM_FORMAT_1I:
+        case GSK_GL_UNIFORM_FORMAT_TEXTURE:
+        case GSK_GL_UNIFORM_FORMAT_1UI:
+          len = 4;
+          break;
+
+        case GSK_GL_UNIFORM_FORMAT_2F:
+        case GSK_GL_UNIFORM_FORMAT_2FV:
+        case GSK_GL_UNIFORM_FORMAT_2I:
+          len = 8;
+          break;
+
+        case GSK_GL_UNIFORM_FORMAT_3F:
+        case GSK_GL_UNIFORM_FORMAT_3FV:
+        case GSK_GL_UNIFORM_FORMAT_3I:
+          len = 12;
+          break;
+
+        case GSK_GL_UNIFORM_FORMAT_4F:
+        case GSK_GL_UNIFORM_FORMAT_4FV:
+        case GSK_GL_UNIFORM_FORMAT_4I:
+          len = 16;
+          break;
+
+
+        case GSK_GL_UNIFORM_FORMAT_MATRIX:
+          len = sizeof (float) * 16;
+          break;
+
+        case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT:
+          len = sizeof (float) * 12;
+          break;
+
+        case GSK_GL_UNIFORM_FORMAT_COLOR:
+          len = sizeof (float) * 4;
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      len *= fu->info.array_count;
+
+      if (memcmp (fdata, sdata, len) != 0)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+gsk_gl_command_queue_dispose (GObject *object)
+{
+  GskGLCommandQueue *self = (GskGLCommandQueue *)object;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  g_clear_object (&self->profiler);
+  g_clear_object (&self->gl_profiler);
+  g_clear_object (&self->context);
+  g_clear_pointer (&self->attachments, gsk_gl_attachment_state_unref);
+  g_clear_pointer (&self->uniforms, gsk_gl_uniform_state_unref);
+
+  gsk_gl_command_batches_clear (&self->batches);
+  gsk_gl_command_binds_clear (&self->batch_binds);
+  gsk_gl_command_uniforms_clear (&self->batch_uniforms);
+
+  gsk_gl_buffer_destroy (&self->vertices);
+
+  G_OBJECT_CLASS (gsk_gl_command_queue_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_command_queue_class_init (GskGLCommandQueueClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_gl_command_queue_dispose;
+}
+
+static void
+gsk_gl_command_queue_init (GskGLCommandQueue *self)
+{
+  self->max_texture_size = -1;
+
+  gsk_gl_command_batches_init (&self->batches, 128);
+  gsk_gl_command_binds_init (&self->batch_binds, 1024);
+  gsk_gl_command_uniforms_init (&self->batch_uniforms, 2048);
+
+  gsk_gl_buffer_init (&self->vertices, GL_ARRAY_BUFFER, sizeof (GskGLDrawVertex));
+}
+
+GskGLCommandQueue *
+gsk_gl_command_queue_new (GdkGLContext      *context,
+                          GskGLUniformState *uniforms)
+{
+  GskGLCommandQueue *self;
+
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  self = g_object_new (GSK_TYPE_GL_COMMAND_QUEUE, NULL);
+  self->context = g_object_ref (context);
+  self->attachments = gsk_gl_attachment_state_new ();
+
+  /* Use shared uniform state if we're provided one */
+  if (uniforms != NULL)
+    self->uniforms = gsk_gl_uniform_state_ref (uniforms);
+  else
+    self->uniforms = gsk_gl_uniform_state_new ();
+
+  /* Determine max texture size immediately and restore context */
+  gdk_gl_context_make_current (context);
+  glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
+
+  return g_steal_pointer (&self);
+}
+
+static inline GskGLCommandBatch *
+begin_next_batch (GskGLCommandQueue *self)
+{
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  /* GskGLCommandBatch contains an embedded linked list using integers into the
+   * self->batches array. We can't use pointer because the batches could be
+   * realloc()'d at runtime.
+   *
+   * Before we execute the command queue, we sort the batches by framebuffer but
+   * leave the batches in place as we can just tweak the links via prev/next.
+   *
+   * Generally we only traverse forwards, so we could ignore the previous field.
+   * But to optimize the reordering of batches by framebuffer we walk backwards
+   * so we sort by most-recently-seen framebuffer to ensure draws happen in the
+   * proper order.
+   */
+
+  batch = gsk_gl_command_batches_append (&self->batches);
+  batch->any.next_batch_index = -1;
+  batch->any.prev_batch_index = self->tail_batch_index;
+
+  return batch;
+}
+
+static void
+enqueue_batch (GskGLCommandQueue *self)
+{
+  guint index;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->batches.len > 0);
+
+  /* Batches are linked lists but using indexes into the batches array instead
+   * of pointers. This is for two main reasons. First, 16-bit indexes allow us
+   * to store the information in 4 bytes, where as two pointers would take 16
+   * bytes.  Furthermore, we have an array here so pointers would get
+   * invalidated if we realloc()'d (and that can happen from time to time).
+   */
+
+  index = self->batches.len - 1;
+
+  if (self->head_batch_index == -1)
+    self->head_batch_index = index;
+
+  if (self->tail_batch_index != -1)
+    {
+      GskGLCommandBatch *prev = &self->batches.items[self->tail_batch_index];
+
+      prev->any.next_batch_index = index;
+    }
+
+  self->tail_batch_index = index;
+}
+
+static void
+discard_batch (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->batches.len > 0);
+
+  self->batches.len--;
+}
+
+void
+gsk_gl_command_queue_begin_draw (GskGLCommandQueue   *self,
+                                 GskGLUniformProgram *program,
+                                 guint                width,
+                                 guint                height)
+{
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->in_draw == FALSE);
+  g_assert (width <= G_MAXUINT16);
+  g_assert (height <= G_MAXUINT16);
+
+  /* Our internal links use 16-bits, so that is our max number
+   * of batches we can have in one frame.
+   */
+  if (will_ignore_batch (self))
+    return;
+
+  self->program_info = program;
+
+  batch = begin_next_batch (self);
+  batch->any.kind = GSK_GL_COMMAND_KIND_DRAW;
+  batch->any.program = program->program_id;
+  batch->any.next_batch_index = -1;
+  batch->any.viewport.width = width;
+  batch->any.viewport.height = height;
+  batch->draw.framebuffer = 0;
+  batch->draw.uniform_count = 0;
+  batch->draw.uniform_offset = self->batch_uniforms.len;
+  batch->draw.bind_count = 0;
+  batch->draw.bind_offset = self->batch_binds.len;
+  batch->draw.vbo_count = 0;
+  batch->draw.vbo_offset = gsk_gl_buffer_get_offset (&self->vertices);
+
+  self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer);
+
+  self->in_draw = TRUE;
+}
+
+void
+gsk_gl_command_queue_end_draw (GskGLCommandQueue *self)
+{
+  GskGLCommandBatch *last_batch;
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->batches.len > 0);
+
+  if (will_ignore_batch (self))
+    return;
+
+  batch = gsk_gl_command_batches_tail (&self->batches);
+
+  g_assert (self->in_draw == TRUE);
+  g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW);
+
+  if G_UNLIKELY (batch->draw.vbo_count == 0)
+    {
+      discard_batch (self);
+      self->in_draw = FALSE;
+      return;
+    }
+
+  /* Track the destination framebuffer in case it changed */
+  batch->draw.framebuffer = self->attachments->fbo.id;
+  self->attachments->fbo.changed = FALSE;
+  self->fbo_max = MAX (self->fbo_max, self->attachments->fbo.id);
+
+  /* Save our full uniform state for this draw so we can possibly
+   * reorder the draw later.
+   */
+  batch->draw.uniform_offset = self->batch_uniforms.len;
+  batch->draw.uniform_count = snapshot_uniforms (self->uniforms, self->program_info, &self->batch_uniforms);
+
+  /* Track the bind attachments that changed */
+  if (self->program_info->has_attachments)
+    {
+      batch->draw.bind_offset = self->batch_binds.len;
+      batch->draw.bind_count = snapshot_attachments (self->attachments, &self->batch_binds);
+    }
+  else
+    {
+      batch->draw.bind_offset = 0;
+      batch->draw.bind_count = 0;
+    }
+
+  if (self->batches.len > 1)
+    last_batch = &self->batches.items[self->batches.len - 2];
+  else
+    last_batch = NULL;
+
+  /* Do simple chaining of draw to last batch. */
+  if (last_batch != NULL &&
+      last_batch->any.kind == GSK_GL_COMMAND_KIND_DRAW &&
+      last_batch->any.program == batch->any.program &&
+      last_batch->any.viewport.width == batch->any.viewport.width &&
+      last_batch->any.viewport.height == batch->any.viewport.height &&
+      last_batch->draw.framebuffer == batch->draw.framebuffer &&
+      last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset &&
+      last_batch->draw.vbo_count + batch->draw.vbo_count <= 0xffff &&
+      snapshots_equal (self, last_batch, batch))
+    {
+      last_batch->draw.vbo_count += batch->draw.vbo_count;
+      discard_batch (self);
+    }
+  else
+    {
+      enqueue_batch (self);
+    }
+
+  self->in_draw = FALSE;
+  self->program_info = NULL;
+}
+
+/**
+ * gsk_gl_command_queue_split_draw:
+ * @self a `GskGLCommandQueue`
+ *
+ * This function is like calling gsk_gl_command_queue_end_draw() followed by
+ * a gsk_gl_command_queue_begin_draw() with the same parameters as a
+ * previous begin draw (if shared uniforms where not changed further).
+ *
+ * This is useful to avoid comparisons inside of loops where we know shared
+ * uniforms are not changing.
+ *
+ * This generally should just be called from gsk_gl_program_split_draw()
+ * as that is where the begin/end flow happens from the render job.
+ */
+void
+gsk_gl_command_queue_split_draw (GskGLCommandQueue *self)
+{
+  GskGLCommandBatch *batch;
+  GskGLUniformProgram *program;
+  guint width;
+  guint height;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->batches.len > 0);
+  g_assert (self->in_draw == TRUE);
+
+  program = self->program_info;
+
+  batch = gsk_gl_command_batches_tail (&self->batches);
+
+  g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW);
+
+  width = batch->any.viewport.width;
+  height = batch->any.viewport.height;
+
+  gsk_gl_command_queue_end_draw (self);
+  gsk_gl_command_queue_begin_draw (self, program, width, height);
+}
+
+void
+gsk_gl_command_queue_clear (GskGLCommandQueue    *self,
+                             guint                  clear_bits,
+                             const graphene_rect_t *viewport)
+{
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->in_draw == FALSE);
+
+  if (will_ignore_batch (self))
+    return;
+
+  if (clear_bits == 0)
+    clear_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+
+  batch = begin_next_batch (self);
+  batch->any.kind = GSK_GL_COMMAND_KIND_CLEAR;
+  batch->any.viewport.width = viewport->size.width;
+  batch->any.viewport.height = viewport->size.height;
+  batch->clear.bits = clear_bits;
+  batch->clear.framebuffer = self->attachments->fbo.id;
+  batch->any.next_batch_index = -1;
+  batch->any.program = 0;
+
+  self->fbo_max = MAX (self->fbo_max, batch->clear.framebuffer);
+
+  enqueue_batch (self);
+
+  self->attachments->fbo.changed = FALSE;
+}
+
+GdkGLContext *
+gsk_gl_command_queue_get_context (GskGLCommandQueue *self)
+{
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL);
+
+  return self->context;
+}
+
+void
+gsk_gl_command_queue_make_current (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (GDK_IS_GL_CONTEXT (self->context));
+
+  gdk_gl_context_make_current (self->context);
+}
+
+void
+gsk_gl_command_queue_delete_program (GskGLCommandQueue *self,
+                                     guint              program)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  glDeleteProgram (program);
+}
+
+static inline void
+apply_viewport (guint *current_width,
+                guint *current_height,
+                guint  width,
+                guint  height)
+{
+  if G_UNLIKELY (*current_width != width || *current_height != height)
+    {
+      *current_width = width;
+      *current_height = height;
+      glViewport (0, 0, width, height);
+    }
+}
+
+static inline void
+apply_scissor (gboolean              *state,
+               guint                  framebuffer,
+               const graphene_rect_t *scissor,
+               gboolean               has_scissor)
+{
+  g_assert (framebuffer != (guint)-1);
+
+  if (framebuffer != 0 || !has_scissor)
+    {
+      if (*state != FALSE)
+        {
+          glDisable (GL_SCISSOR_TEST);
+          *state = FALSE;
+        }
+    }
+  else
+    {
+      if (*state != TRUE)
+        {
+          glEnable (GL_SCISSOR_TEST);
+          glScissor (scissor->origin.x,
+                     scissor->origin.y,
+                     scissor->size.width,
+                     scissor->size.height);
+          *state = TRUE;
+        }
+    }
+}
+
+static inline gboolean
+apply_framebuffer (int   *framebuffer,
+                   guint  new_framebuffer)
+{
+  if G_UNLIKELY (new_framebuffer != *framebuffer)
+    {
+      *framebuffer = new_framebuffer;
+      glBindFramebuffer (GL_FRAMEBUFFER, new_framebuffer);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static inline void
+gsk_gl_command_queue_unlink (GskGLCommandQueue *self,
+                             GskGLCommandBatch *batch)
+{
+  if (batch->any.prev_batch_index == -1)
+    self->head_batch_index = batch->any.next_batch_index;
+  else
+    self->batches.items[batch->any.prev_batch_index].any.next_batch_index = batch->any.next_batch_index;
+
+  if (batch->any.next_batch_index == -1)
+    self->tail_batch_index = batch->any.prev_batch_index;
+  else
+    self->batches.items[batch->any.next_batch_index].any.prev_batch_index = batch->any.prev_batch_index;
+
+  batch->any.prev_batch_index = -1;
+  batch->any.next_batch_index = -1;
+}
+
+static inline void
+gsk_gl_command_queue_insert_before (GskGLCommandQueue *self,
+                                    GskGLCommandBatch *batch,
+                                    GskGLCommandBatch *sibling)
+{
+  int sibling_index;
+  int index;
+
+  g_assert (batch >= self->batches.items);
+  g_assert (batch < &self->batches.items[self->batches.len]);
+  g_assert (sibling >= self->batches.items);
+  g_assert (sibling < &self->batches.items[self->batches.len]);
+
+  index = gsk_gl_command_batches_index_of (&self->batches, batch);
+  sibling_index = gsk_gl_command_batches_index_of (&self->batches, sibling);
+
+  batch->any.next_batch_index = sibling_index;
+  batch->any.prev_batch_index = sibling->any.prev_batch_index;
+
+  if (batch->any.prev_batch_index > -1)
+    self->batches.items[batch->any.prev_batch_index].any.next_batch_index = index;
+
+  sibling->any.prev_batch_index = index;
+
+  if (batch->any.prev_batch_index == -1)
+    self->head_batch_index = index;
+}
+
+static void
+gsk_gl_command_queue_sort_batches (GskGLCommandQueue *self)
+{
+  int *seen;
+  int *seen_free = NULL;
+  int index;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->tail_batch_index >= 0);
+  g_assert (self->fbo_max >= 0);
+
+  /* Create our seen list with most recent index set to -1,
+   * meaning we haven't yet seen that framebuffer.
+   */
+  if (self->fbo_max < 1024)
+    seen = g_alloca (sizeof (int) * (self->fbo_max + 1));
+  else
+    seen = seen_free = g_new0 (int, (self->fbo_max + 1));
+  for (int i = 0; i <= self->fbo_max; i++)
+    seen[i] = -1;
+
+  /* Walk in reverse, and if we've seen that framebuffer before, we want to
+   * delay this operation until right before the last batch we saw for that
+   * framebuffer.
+   *
+   * We can do this because we don't use a framebuffer's texture until it has
+   * been completely drawn.
+   */
+  index = self->tail_batch_index;
+
+  while (index >= 0)
+    {
+      GskGLCommandBatch *batch = &self->batches.items[index];
+      int cur_index = index;
+      int fbo = -1;
+
+      g_assert (index > -1);
+      g_assert (index < self->batches.len);
+
+      switch (batch->any.kind)
+        {
+        case GSK_GL_COMMAND_KIND_DRAW:
+          fbo = batch->draw.framebuffer;
+          break;
+
+        case GSK_GL_COMMAND_KIND_CLEAR:
+          fbo = batch->clear.framebuffer;
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      index = batch->any.prev_batch_index;
+
+      g_assert (index >= -1);
+      g_assert (index < (int)self->batches.len);
+      g_assert (fbo >= -1);
+
+      if (fbo == -1)
+        continue;
+
+      g_assert (fbo <= self->fbo_max);
+      g_assert (seen[fbo] >= -1);
+      g_assert (seen[fbo] < (int)self->batches.len);
+
+      if (seen[fbo] != -1 && seen[fbo] != batch->any.next_batch_index)
+        {
+          int mru_index = seen[fbo];
+          GskGLCommandBatch *mru = &self->batches.items[mru_index];
+
+          g_assert (mru_index > -1);
+
+          gsk_gl_command_queue_unlink (self, batch);
+
+          g_assert (batch->any.prev_batch_index == -1);
+          g_assert (batch->any.next_batch_index == -1);
+
+          gsk_gl_command_queue_insert_before (self, batch, mru);
+
+          g_assert (batch->any.prev_batch_index > -1 ||
+                    self->head_batch_index == cur_index);
+          g_assert (batch->any.next_batch_index == seen[fbo]);
+        }
+
+      g_assert (cur_index > -1);
+      g_assert (seen[fbo] >= -1);
+
+      seen[fbo] = cur_index;
+    }
+
+  g_free (seen_free);
+}
+
+/**
+ * gsk_gl_command_queue_execute:
+ * @self: a `GskGLCommandQueue`
+ * @surface_height: the height of the backing surface
+ * @scale_factor: the scale factor of the backing surface
+ * #scissor: (nullable): the scissor clip if any
+ *
+ * Executes all of the batches in the command queue.
+ */
+void
+gsk_gl_command_queue_execute (GskGLCommandQueue    *self,
+                              guint                 surface_height,
+                              guint                 scale_factor,
+                              const cairo_region_t *scissor)
+{
+  G_GNUC_UNUSED guint count = 0;
+  graphene_rect_t scissor_test;
+  gboolean has_scissor = scissor != NULL;
+  gboolean scissor_state = -1;
+  guint program = 0;
+  guint width = 0;
+  guint height = 0;
+  guint n_binds = 0;
+  guint n_fbos = 0;
+  guint n_uniforms = 0;
+  guint n_programs = 0;
+  guint vao_id;
+  guint vbo_id;
+  int textures[4];
+  int framebuffer = -1;
+  int next_batch_index;
+  int active = -1;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->in_draw == FALSE);
+
+  if (self->batches.len == 0)
+    return;
+
+  for (guint i = 0; i < G_N_ELEMENTS (textures); i++)
+    textures[i] = -1;
+
+  gsk_gl_command_queue_sort_batches (self);
+
+  gsk_gl_command_queue_make_current (self);
+
+#ifdef G_ENABLE_DEBUG
+  gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
+  gsk_profiler_timer_begin (self->profiler, self->metrics.cpu_time);
+#endif
+
+  glEnable (GL_DEPTH_TEST);
+  glDepthFunc (GL_LEQUAL);
+
+  /* Pre-multiplied alpha */
+  glEnable (GL_BLEND);
+  glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+  glBlendEquation (GL_FUNC_ADD);
+
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  vbo_id = gsk_gl_buffer_submit (&self->vertices);
+
+  /* 0 = position location */
+  glEnableVertexAttribArray (0);
+  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, position));
+
+  /* 1 = texture coord location */
+  glEnableVertexAttribArray (1);
+  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, uv));
+
+  /* 2 = color location */
+  glEnableVertexAttribArray (2);
+  glVertexAttribPointer (2, 4, GL_HALF_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, color));
+
+  /* 3 = color2 location */
+  glEnableVertexAttribArray (3);
+  glVertexAttribPointer (3, 4, GL_HALF_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, color2));
+
+  /* Setup initial scissor clip */
+  if (scissor != NULL)
+    {
+      cairo_rectangle_int_t r;
+
+      g_assert (cairo_region_num_rectangles (scissor) == 1);
+      cairo_region_get_rectangle (scissor, 0, &r);
+
+      scissor_test.origin.x = r.x * scale_factor;
+      scissor_test.origin.y = surface_height - (r.height * scale_factor) - (r.y * scale_factor);
+      scissor_test.size.width = r.width * scale_factor;
+      scissor_test.size.height = r.height * scale_factor;
+    }
+
+  next_batch_index = self->head_batch_index;
+
+  while (next_batch_index >= 0)
+    {
+      const GskGLCommandBatch *batch = &self->batches.items[next_batch_index];
+
+      g_assert (next_batch_index >= 0);
+      g_assert (next_batch_index < self->batches.len);
+      g_assert (batch->any.next_batch_index != next_batch_index);
+
+      count++;
+
+      switch (batch->any.kind)
+        {
+        case GSK_GL_COMMAND_KIND_CLEAR:
+          if (apply_framebuffer (&framebuffer, batch->clear.framebuffer))
+            {
+              apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
+              n_fbos++;
+            }
+
+          apply_viewport (&width,
+                          &height,
+                          batch->any.viewport.width,
+                          batch->any.viewport.height);
+
+          glClearColor (0, 0, 0, 0);
+          glClear (batch->clear.bits);
+        break;
+
+        case GSK_GL_COMMAND_KIND_DRAW:
+          if (batch->any.program != program)
+            {
+              program = batch->any.program;
+              glUseProgram (program);
+
+              n_programs++;
+            }
+
+          if (apply_framebuffer (&framebuffer, batch->draw.framebuffer))
+            {
+              apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
+              n_fbos++;
+            }
+
+          apply_viewport (&width,
+                          &height,
+                          batch->any.viewport.width,
+                          batch->any.viewport.height);
+
+          if G_UNLIKELY (batch->draw.bind_count > 0)
+            {
+              const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset];
+
+              for (guint i = 0; i < batch->draw.bind_count; i++)
+                {
+                  if (textures[bind->texture] != bind->id)
+                    {
+                      if (active != bind->texture)
+                        {
+                          active = bind->texture;
+                          glActiveTexture (GL_TEXTURE0 + bind->texture);
+                        }
+
+                      glBindTexture (GL_TEXTURE_2D, bind->id);
+                      textures[bind->texture] = bind->id;
+                    }
+
+                  bind++;
+                }
+
+              n_binds += batch->draw.bind_count;
+            }
+
+          if (batch->draw.uniform_count > 0)
+            {
+              const GskGLCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset];
+
+              for (guint i = 0; i < batch->draw.uniform_count; i++, u++)
+                gsk_gl_uniform_state_apply (self->uniforms, program, u->location, u->info);
+
+              n_uniforms += batch->draw.uniform_count;
+            }
+
+          glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count);
+
+        break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+#if 0
+      if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ||
+          batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR)
+        {
+          char filename[128];
+          g_snprintf (filename, sizeof filename,
+                      "capture%03u_batch%03d_kind%u_program%u_u%u_b%u_fb%u_ctx%p.png",
+                      count, next_batch_index,
+                      batch->any.kind, batch->any.program,
+                      batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0,
+                      batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0,
+                      framebuffer,
+                      gdk_gl_context_get_current ());
+          gsk_gl_command_queue_capture_png (self, filename, width, height, TRUE);
+          gsk_gl_command_queue_print_batch (self, batch);
+        }
+#endif
+
+      next_batch_index = batch->any.next_batch_index;
+    }
+
+  glDeleteBuffers (1, &vbo_id);
+  glDeleteVertexArrays (1, &vao_id);
+
+  gdk_profiler_set_int_counter (self->metrics.n_binds, n_binds);
+  gdk_profiler_set_int_counter (self->metrics.n_uniforms, n_uniforms);
+  gdk_profiler_set_int_counter (self->metrics.n_fbos, n_fbos);
+  gdk_profiler_set_int_counter (self->metrics.n_programs, n_programs);
+  gdk_profiler_set_int_counter (self->metrics.n_uploads, self->n_uploads);
+  gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len);
+
+#ifdef G_ENABLE_DEBUG
+  {
+    gint64 start_time G_GNUC_UNUSED = gsk_profiler_timer_get_start (self->profiler, self->metrics.cpu_time);
+    gint64 cpu_time = gsk_profiler_timer_end (self->profiler, self->metrics.cpu_time);
+    gint64 gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
+
+    gsk_profiler_timer_set (self->profiler, self->metrics.gpu_time, gpu_time);
+    gsk_profiler_timer_set (self->profiler, self->metrics.cpu_time, cpu_time);
+    gsk_profiler_counter_inc (self->profiler, self->metrics.n_frames);
+
+    gsk_profiler_push_samples (self->profiler);
+  }
+#endif
+}
+
+void
+gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->batches.len == 0);
+
+  gsk_gl_command_queue_make_current (self);
+
+  self->fbo_max = 0;
+  self->tail_batch_index = -1;
+  self->head_batch_index = -1;
+  self->in_frame = TRUE;
+}
+
+/**
+ * gsk_gl_command_queue_end_frame:
+ * @self: a `GskGLCommandQueue`
+ *
+ * This function performs cleanup steps that need to be done after
+ * a frame has finished. This is not performed as part of the command
+ * queue execution to allow for the frame to be submitted as soon
+ * as possible.
+ *
+ * However, it should be executed after the draw contexts end_frame
+ * has been called to swap the OpenGL framebuffers.
+ */
+void
+gsk_gl_command_queue_end_frame (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_command_queue_make_current (self);
+  gsk_gl_uniform_state_end_frame (self->uniforms);
+
+  /* Reset attachments so we don't hold on to any textures
+   * that might be released after the frame.
+   */
+  for (guint i = 0; i < G_N_ELEMENTS (self->attachments->textures); i++)
+    {
+      if (self->attachments->textures[i].id != 0)
+        {
+          glActiveTexture (GL_TEXTURE0 + i);
+          glBindTexture (GL_TEXTURE_2D, 0);
+
+          self->attachments->textures[i].id = 0;
+          self->attachments->textures[i].changed = FALSE;
+          self->attachments->textures[i].initial = TRUE;
+        }
+    }
+
+  self->batches.len = 0;
+  self->batch_binds.len = 0;
+  self->batch_uniforms.len = 0;
+  self->n_uploads = 0;
+  self->tail_batch_index = -1;
+  self->in_frame = FALSE;
+}
+
+gboolean
+gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self,
+                                           int                width,
+                                           int                height,
+                                           int                format,
+                                           int                min_filter,
+                                           int                mag_filter,
+                                           guint             *out_fbo_id,
+                                           guint             *out_texture_id)
+{
+  GLuint fbo_id = 0;
+  GLint texture_id;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (width > 0);
+  g_assert (height > 0);
+  g_assert (out_fbo_id != NULL);
+  g_assert (out_texture_id != NULL);
+
+  texture_id = gsk_gl_command_queue_create_texture (self,
+                                                     width, height,
+                                                     format,
+                                                     min_filter, mag_filter);
+
+  if (texture_id == -1)
+    {
+      *out_fbo_id = 0;
+      *out_texture_id = 0;
+      return FALSE;
+    }
+
+  fbo_id = gsk_gl_command_queue_create_framebuffer (self);
+
+  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
+  g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
+
+  *out_fbo_id = fbo_id;
+  *out_texture_id = texture_id;
+
+  return TRUE;
+}
+
+int
+gsk_gl_command_queue_create_texture (GskGLCommandQueue *self,
+                                     int                width,
+                                     int                height,
+                                     int                format,
+                                     int                min_filter,
+                                     int                mag_filter)
+{
+  GLuint texture_id = 0;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  if G_UNLIKELY (self->max_texture_size == -1)
+    glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
+
+  if (width > self->max_texture_size || height > self->max_texture_size)
+    return -1;
+
+  glGenTextures (1, &texture_id);
+
+  glActiveTexture (GL_TEXTURE0);
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+  if (gdk_gl_context_get_use_es (self->context))
+    glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+  else
+    glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+  /* Restore the previous texture if it was set */
+  if (self->attachments->textures[0].id != 0)
+    glBindTexture (GL_TEXTURE_2D, self->attachments->textures[0].id);
+
+  return (int)texture_id;
+}
+
+guint
+gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self)
+{
+  GLuint fbo_id;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  glGenFramebuffers (1, &fbo_id);
+
+  return fbo_id;
+}
+
+int
+gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self,
+                                     GdkTexture        *texture,
+                                     guint              x_offset,
+                                     guint              y_offset,
+                                     guint              width,
+                                     guint              height,
+                                     int                min_filter,
+                                     int                mag_filter)
+{
+  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+  cairo_surface_t *surface = NULL;
+  GdkMemoryFormat data_format;
+  const guchar *data;
+  gsize data_stride;
+  gsize bpp;
+  int texture_id;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (!GDK_IS_GL_TEXTURE (texture));
+  g_assert (x_offset + width <= gdk_texture_get_width (texture));
+  g_assert (y_offset + height <= gdk_texture_get_height (texture));
+  g_assert (min_filter == GL_LINEAR || min_filter == GL_NEAREST);
+  g_assert (mag_filter == GL_LINEAR || min_filter == GL_NEAREST);
+
+  if (width > self->max_texture_size || height > self->max_texture_size)
+    {
+      g_warning ("Attempt to create texture of size %ux%u but max size is %d. "
+                 "Clipping will occur.",
+                 width, height, self->max_texture_size);
+      width = MAX (width, self->max_texture_size);
+      height = MAX (height, self->max_texture_size);
+    }
+
+  texture_id = gsk_gl_command_queue_create_texture (self, width, height, GL_RGBA8, min_filter, mag_filter);
+  if (texture_id == -1)
+    return texture_id;
+
+  if (GDK_IS_MEMORY_TEXTURE (texture))
+    {
+      GdkMemoryTexture *memory_texture = GDK_MEMORY_TEXTURE (texture);
+      data = gdk_memory_texture_get_data (memory_texture);
+      data_format = gdk_texture_get_format (texture);
+      data_stride = gdk_memory_texture_get_stride (memory_texture);
+    }
+  else
+    {
+      /* Fall back to downloading to a surface */
+      surface = gdk_texture_download_surface (texture);
+      cairo_surface_flush (surface);
+      data = cairo_image_surface_get_data (surface);
+      data_format = GDK_MEMORY_DEFAULT;
+      data_stride = cairo_image_surface_get_stride (surface);
+    }
+
+  self->n_uploads++;
+
+  bpp = gdk_memory_format_bytes_per_pixel (data_format);
+
+  /* Switch to texture0 as 2D. We'll restore it later. */
+  glActiveTexture (GL_TEXTURE0);
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  gdk_gl_context_upload_texture (gdk_gl_context_get_current (),
+                                 data + x_offset * bpp + y_offset * data_stride,
+                                 width, height, data_stride,
+                                 data_format, GL_TEXTURE_2D);
+
+  /* Restore previous texture state if any */
+  if (self->attachments->textures[0].id > 0)
+    glBindTexture (self->attachments->textures[0].target,
+                   self->attachments->textures[0].id);
+
+  g_clear_pointer (&surface, cairo_surface_destroy);
+
+  if (gdk_profiler_is_running ())
+    gdk_profiler_add_markf (start_time, GDK_PROFILER_CURRENT_TIME-start_time,
+                            "Upload Texture",
+                            "Size %dx%d", width, height);
+
+  return texture_id;
+}
+
+void
+gsk_gl_command_queue_set_profiler (GskGLCommandQueue *self,
+                                   GskProfiler       *profiler)
+{
+#ifdef G_ENABLE_DEBUG
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (GSK_IS_PROFILER (profiler));
+
+  if (g_set_object (&self->profiler, profiler))
+    {
+      self->gl_profiler = gsk_gl_profiler_new (self->context);
+
+      self->metrics.n_frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
+      self->metrics.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU Time", FALSE, TRUE);
+      self->metrics.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU Time", FALSE, TRUE);
+
+      self->metrics.n_binds = gdk_profiler_define_int_counter ("attachments", "Number of texture attachments");
+      self->metrics.n_fbos = gdk_profiler_define_int_counter ("fbos", "Number of framebuffers attached");
+      self->metrics.n_uniforms = gdk_profiler_define_int_counter ("uniforms", "Number of uniforms changed");
+      self->metrics.n_uploads = gdk_profiler_define_int_counter ("uploads", "Number of texture uploads");
+      self->metrics.n_programs = gdk_profiler_define_int_counter ("programs", "Number of program changes");
+      self->metrics.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth", "Depth of GL command batches");
+    }
+#endif
+}
diff --git a/gsk/gl/gskglcommandqueueprivate.h b/gsk/gl/gskglcommandqueueprivate.h
new file mode 100644 (file)
index 0000000..c4da723
--- /dev/null
@@ -0,0 +1,362 @@
+/* gskglcommandqueueprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_COMMAND_QUEUE_PRIVATE_H__
+#define __GSK_GL_COMMAND_QUEUE_PRIVATE_H__
+
+#include <gsk/gskprofilerprivate.h>
+
+#include "gskgltypesprivate.h"
+#include "gskglbufferprivate.h"
+#include "gskglattachmentstateprivate.h"
+#include "gskgluniformstateprivate.h"
+
+#include "inlinearray.h"
+
+#include "gskglprofilerprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_gl_command_queue_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLCommandQueue, gsk_gl_command_queue, GSK, GL_COMMAND_QUEUE, GObject)
+
+typedef enum _GskGLCommandKind
+{
+  /* The batch will perform a glClear() */
+  GSK_GL_COMMAND_KIND_CLEAR,
+
+  /* The batch will perform a glDrawArrays() */
+  GSK_GL_COMMAND_KIND_DRAW,
+} GskGLCommandKind;
+
+typedef struct _GskGLCommandBind
+{
+  /* @texture is the value passed to glActiveTexture(), the "slot" the
+   * texture will be placed into. We always use GL_TEXTURE_2D so we don't
+   * waste any bits here to indicate that.
+   */
+  guint texture : 5;
+
+  /* The identifier for the texture created with glGenTextures(). */
+  guint id : 27;
+} GskGLCommandBind;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandBind) == 4);
+
+typedef struct _GskGLCommandBatchAny
+{
+  /* A GskGLCommandKind indicating what the batch will do */
+  guint kind : 8;
+
+  /* The program's identifier to use for determining if we can merge two
+   * batches together into a single set of draw operations. We put this
+   * here instead of the GskGLCommandDraw so that we can use the extra
+   * bits here without making the structure larger.
+   */
+  guint program : 24;
+
+  /* The index of the next batch following this one. This is used
+   * as a sort of integer-based linked list to simplify out-of-order
+   * batching without moving memory around. -1 indicates last batch.
+   */
+  gint16 next_batch_index;
+
+  /* Same but for reverse direction as we sort in reverse to get the
+   * batches ordered by framebuffer.
+   */
+  gint16 prev_batch_index;
+
+  /* The viewport size of the batch. We check this as we process
+   * batches to determine if we need to resize the viewport.
+   */
+  struct {
+    guint16 width;
+    guint16 height;
+  } viewport;
+} GskGLCommandBatchAny;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandBatchAny) == 12);
+
+typedef struct _GskGLCommandDraw
+{
+  GskGLCommandBatchAny head;
+
+  /* There doesn't seem to be a limit on the framebuffer identifier that
+   * can be returned, so we have to use a whole unsigned for the framebuffer
+   * we are drawing to. When processing batches, we check to see if this
+   * changes and adjust the render target accordingly. Some sorting is
+   * performed to reduce the amount we change framebuffers.
+   */
+  guint framebuffer;
+
+  /* The number of uniforms to change. This must be less than or equal to
+   * GL_MAX_UNIFORM_LOCATIONS but only guaranteed up to 1024 by any OpenGL
+   * implementation to be conformant.
+   */
+  guint uniform_count : 11;
+
+  /* The number of textures to bind, which is only guaranteed up to 16
+   * by the OpenGL specification to be conformant.
+   */
+  guint bind_count : 5;
+
+  /* GL_MAX_ELEMENTS_VERTICES specifies 33000 for this which requires 16-bit
+   * to address all possible counts <= GL_MAX_ELEMENTS_VERTICES.
+   */
+  guint vbo_count : 16;
+
+  /* The offset within the VBO containing @vbo_count vertices to send with
+   * glDrawArrays().
+   */
+  guint vbo_offset;
+
+  /* The offset within the array of uniform changes to be made containing
+   * @uniform_count `GskGLCommandUniform` elements to apply.
+   */
+  guint uniform_offset;
+
+  /* The offset within the array of bind changes to be made containing
+   * @bind_count `GskGLCommandBind` elements to apply.
+   */
+  guint bind_offset;
+} GskGLCommandDraw;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandDraw) == 32);
+
+typedef struct _GskGLCommandClear
+{
+  GskGLCommandBatchAny  any;
+  guint                 bits;
+  guint                 framebuffer;
+} GskGLCommandClear;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandClear) == 20);
+
+typedef struct _GskGLCommandUniform
+{
+  GskGLUniformInfo info;
+  guint             location;
+} GskGLCommandUniform;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandUniform) == 8);
+
+typedef union _GskGLCommandBatch
+{
+  GskGLCommandBatchAny any;
+  GskGLCommandDraw     draw;
+  GskGLCommandClear    clear;
+} GskGLCommandBatch;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandBatch) == 32);
+
+DEFINE_INLINE_ARRAY (GskGLCommandBatches, gsk_gl_command_batches, GskGLCommandBatch)
+DEFINE_INLINE_ARRAY (GskGLCommandBinds, gsk_gl_command_binds, GskGLCommandBind)
+DEFINE_INLINE_ARRAY (GskGLCommandUniforms, gsk_gl_command_uniforms, GskGLCommandUniform)
+
+struct _GskGLCommandQueue
+{
+  GObject parent_instance;
+
+  /* The GdkGLContext we make current before executing GL commands. */
+  GdkGLContext *context;
+
+  /* Array of GskGLCommandBatch which is a fixed size structure that will
+   * point into offsets of other arrays so that all similar data is stored
+   * together. The idea here is that we reduce the need for pointers so that
+   * using g_realloc()'d arrays is fine.
+   */
+  GskGLCommandBatches batches;
+
+  /* Contains array of vertices and some wrapper code to help upload them
+   * to the GL driver. We can also tweak this to use double buffered arrays
+   * if we find that to be faster on some hardware and/or drivers.
+   */
+  GskGLBuffer vertices;
+
+  /* The GskGLAttachmentState contains information about our FBO and texture
+   * attachments as we process incoming operations. We snapshot them into
+   * various batches so that we can compare differences between merge
+   * candidates.
+   */
+  GskGLAttachmentState *attachments;
+
+  /* The uniform state across all programs. We snapshot this into batches so
+   * that we can compare uniform state between batches to give us more
+   * chances at merging draw commands.
+   */
+  GskGLUniformState *uniforms;
+
+  /* Current program if we are in a draw so that we can send commands
+   * to the uniform state as needed.
+   */
+  GskGLUniformProgram *program_info;
+
+  /* The profiler instance to deliver timing/etc data */
+  GskProfiler *profiler;
+  GskGLProfiler *gl_profiler;
+
+  /* Array of GskGLCommandBind which denote what textures need to be attached
+   * to which slot. GskGLCommandDraw.bind_offset and bind_count reference this
+   * array to determine what to attach.
+   */
+  GskGLCommandBinds batch_binds;
+
+  /* Array of GskGLCommandUniform denoting which uniforms must be updated
+   * before the glDrawArrays() may be called. These are referenced from the
+   * GskGLCommandDraw.uniform_offset and uniform_count fields.
+   */
+  GskGLCommandUniforms batch_uniforms;
+
+  /* Discovered max texture size when loading the command queue so that we
+   * can either scale down or slice textures to fit within this size. Assumed
+   * to be both height and width.
+   */
+  int max_texture_size;
+
+  /* The index of the last batch in @batches, which may not be the element
+   * at the end of the array, as batches can be reordered. This is used to
+   * update the "next" index when adding a new batch.
+   */
+  gint16 tail_batch_index;
+  gint16 head_batch_index;
+
+  /* Max framebuffer we used, so we can sort items faster */
+  guint fbo_max;
+
+  /* Various GSK and GDK metric counter ids */
+  struct {
+    GQuark n_frames;
+    GQuark cpu_time;
+    GQuark gpu_time;
+    guint n_binds;
+    guint n_fbos;
+    guint n_uniforms;
+    guint n_uploads;
+    guint n_programs;
+    guint queue_depth;
+  } metrics;
+
+  /* Counter for uploads on the frame */
+  guint n_uploads;
+
+  /* If we're inside a begin/end_frame pair */
+  guint in_frame : 1;
+
+  /* If we're inside of a begin_draw()/end_draw() pair. */
+  guint in_draw : 1;
+
+  /* If we've warned about truncating batches */
+  guint have_truncated : 1;
+};
+
+GskGLCommandQueue *gsk_gl_command_queue_new                   (GdkGLContext         *context,
+                                                               GskGLUniformState    *uniforms);
+void                gsk_gl_command_queue_set_profiler         (GskGLCommandQueue    *self,
+                                                               GskProfiler          *profiler);
+GdkGLContext       *gsk_gl_command_queue_get_context          (GskGLCommandQueue    *self);
+void                gsk_gl_command_queue_make_current         (GskGLCommandQueue    *self);
+void                gsk_gl_command_queue_begin_frame          (GskGLCommandQueue    *self);
+void                gsk_gl_command_queue_end_frame            (GskGLCommandQueue    *self);
+void                gsk_gl_command_queue_execute              (GskGLCommandQueue    *self,
+                                                               guint                 surface_height,
+                                                               guint                 scale_factor,
+                                                               const cairo_region_t *scissor);
+int                 gsk_gl_command_queue_upload_texture       (GskGLCommandQueue    *self,
+                                                               GdkTexture           *texture,
+                                                               guint                 x_offset,
+                                                               guint                 y_offset,
+                                                               guint                 width,
+                                                               guint                 height,
+                                                               int                   min_filter,
+                                                               int                   mag_filter);
+int                 gsk_gl_command_queue_create_texture       (GskGLCommandQueue    *self,
+                                                               int                   width,
+                                                               int                   height,
+                                                               int                   format,
+                                                               int                   min_filter,
+                                                               int                   mag_filter);
+guint               gsk_gl_command_queue_create_framebuffer   (GskGLCommandQueue    *self);
+gboolean            gsk_gl_command_queue_create_render_target (GskGLCommandQueue    *self,
+                                                               int                   width,
+                                                               int                   height,
+                                                               int                   format,
+                                                               int                   min_filter,
+                                                               int                   mag_filter,
+                                                               guint                *out_fbo_id,
+                                                               guint                *out_texture_id);
+void                gsk_gl_command_queue_delete_program       (GskGLCommandQueue    *self,
+                                                               guint                 program_id);
+void                gsk_gl_command_queue_clear                (GskGLCommandQueue    *self,
+                                                               guint                 clear_bits,
+                                                               const graphene_rect_t *viewport);
+void                gsk_gl_command_queue_begin_draw           (GskGLCommandQueue    *self,
+                                                               GskGLUniformProgram  *program_info,
+                                                               guint                 width,
+                                                               guint                 height);
+void                gsk_gl_command_queue_end_draw             (GskGLCommandQueue    *self);
+void                gsk_gl_command_queue_split_draw           (GskGLCommandQueue    *self);
+
+static inline GskGLCommandBatch *
+gsk_gl_command_queue_get_batch (GskGLCommandQueue *self)
+{
+  return gsk_gl_command_batches_tail (&self->batches);
+}
+
+static inline GskGLDrawVertex *
+gsk_gl_command_queue_add_vertices (GskGLCommandQueue *self)
+{
+  gsk_gl_command_queue_get_batch (self)->draw.vbo_count += GSK_GL_N_VERTICES;
+  return gsk_gl_buffer_advance (&self->vertices, GSK_GL_N_VERTICES);
+}
+
+static inline GskGLDrawVertex *
+gsk_gl_command_queue_add_n_vertices (GskGLCommandQueue *self,
+                                     guint              count)
+{
+  /* This is a batch form of gsk_gl_command_queue_add_vertices(). Note that
+   * it does *not* add the count to .draw.vbo_count as the caller is responsible
+   * for that.
+   */
+  return gsk_gl_buffer_advance (&self->vertices, GSK_GL_N_VERTICES * count);
+}
+
+static inline void
+gsk_gl_command_queue_retract_n_vertices (GskGLCommandQueue *self,
+                                         guint              count)
+{
+  /* Like gsk_gl_command_queue_add_n_vertices(), this does not tweak
+   * the draw vbo_count.
+   */
+  gsk_gl_buffer_retract (&self->vertices, GSK_GL_N_VERTICES * count);
+}
+
+static inline guint
+gsk_gl_command_queue_bind_framebuffer (GskGLCommandQueue *self,
+                                       guint              framebuffer)
+{
+  guint ret = self->attachments->fbo.id;
+  gsk_gl_attachment_state_bind_framebuffer (self->attachments, framebuffer);
+  return ret;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_COMMAND_QUEUE_PRIVATE_H__ */
diff --git a/gsk/gl/gskglcompiler.c b/gsk/gl/gskglcompiler.c
new file mode 100644 (file)
index 0000000..c885df8
--- /dev/null
@@ -0,0 +1,683 @@
+/* gskglcompiler.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gsk/gskdebugprivate.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskglcompilerprivate.h"
+#include "gskglprogramprivate.h"
+
+#define SHADER_VERSION_GLES       100
+#define SHADER_VERSION_GL2_LEGACY 110
+#define SHADER_VERSION_GL3_LEGACY 130
+#define SHADER_VERSION_GL3        150
+
+struct _GskGLCompiler
+{
+  GObject parent_instance;
+
+  GskGLDriver *driver;
+
+  GBytes *all_preamble;
+  GBytes *fragment_preamble;
+  GBytes *vertex_preamble;
+  GBytes *fragment_source;
+  GBytes *fragment_suffix;
+  GBytes *vertex_source;
+  GBytes *vertex_suffix;
+
+  GArray *attrib_locations;
+
+  int glsl_version;
+
+  guint gl3 : 1;
+  guint gles : 1;
+  guint legacy : 1;
+  guint debug_shaders : 1;
+};
+
+typedef struct _GskGLProgramAttrib
+{
+  const char *name;
+  guint location;
+} GskGLProgramAttrib;
+
+static GBytes *empty_bytes;
+
+G_DEFINE_TYPE (GskGLCompiler, gsk_gl_compiler, G_TYPE_OBJECT)
+
+static void
+gsk_gl_compiler_finalize (GObject *object)
+{
+  GskGLCompiler *self = (GskGLCompiler *)object;
+
+  g_clear_pointer (&self->all_preamble, g_bytes_unref);
+  g_clear_pointer (&self->fragment_preamble, g_bytes_unref);
+  g_clear_pointer (&self->vertex_preamble, g_bytes_unref);
+  g_clear_pointer (&self->vertex_suffix, g_bytes_unref);
+  g_clear_pointer (&self->fragment_source, g_bytes_unref);
+  g_clear_pointer (&self->fragment_suffix, g_bytes_unref);
+  g_clear_pointer (&self->vertex_source, g_bytes_unref);
+  g_clear_pointer (&self->attrib_locations, g_array_unref);
+  g_clear_object (&self->driver);
+
+  G_OBJECT_CLASS (gsk_gl_compiler_parent_class)->finalize (object);
+}
+
+static void
+gsk_gl_compiler_class_init (GskGLCompilerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gsk_gl_compiler_finalize;
+
+  empty_bytes = g_bytes_new (NULL, 0);
+}
+
+static void
+gsk_gl_compiler_init (GskGLCompiler *self)
+{
+  self->glsl_version = 150;
+  self->attrib_locations = g_array_new (FALSE, FALSE, sizeof (GskGLProgramAttrib));
+  self->all_preamble = g_bytes_ref (empty_bytes);
+  self->vertex_preamble = g_bytes_ref (empty_bytes);
+  self->fragment_preamble = g_bytes_ref (empty_bytes);
+  self->vertex_source = g_bytes_ref (empty_bytes);
+  self->vertex_suffix = g_bytes_ref (empty_bytes);
+  self->fragment_source = g_bytes_ref (empty_bytes);
+  self->fragment_suffix = g_bytes_ref (empty_bytes);
+}
+
+GskGLCompiler *
+gsk_gl_compiler_new (GskGLDriver *driver,
+                     gboolean     debug_shaders)
+{
+  GskGLCompiler *self;
+  GdkGLContext *context;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+  g_return_val_if_fail (driver->shared_command_queue != NULL, NULL);
+
+  self = g_object_new (GSK_TYPE_GL_COMPILER, NULL);
+  self->driver = g_object_ref (driver);
+  self->debug_shaders = !!debug_shaders;
+
+  context = gsk_gl_command_queue_get_context (self->driver->shared_command_queue);
+
+  if (gdk_gl_context_get_use_es (context))
+    {
+      self->glsl_version = SHADER_VERSION_GLES;
+      self->gles = TRUE;
+    }
+  else if (gdk_gl_context_is_legacy (context))
+    {
+      int maj, min;
+
+      gdk_gl_context_get_version (context, &maj, &min);
+
+      if (maj == 3)
+        self->glsl_version = SHADER_VERSION_GL3_LEGACY;
+      else
+        self->glsl_version = SHADER_VERSION_GL2_LEGACY;
+
+      self->legacy = TRUE;
+    }
+  else
+    {
+      self->glsl_version = SHADER_VERSION_GL3;
+      self->gl3 = TRUE;
+    }
+
+  gsk_gl_command_queue_make_current (self->driver->shared_command_queue);
+
+  return g_steal_pointer (&self);
+}
+
+void
+gsk_gl_compiler_bind_attribute (GskGLCompiler *self,
+                                const char    *name,
+                                guint          location)
+{
+  GskGLProgramAttrib attrib;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (name != NULL);
+  g_return_if_fail (location < 32);
+
+  attrib.name = g_intern_string (name);
+  attrib.location = location;
+
+  g_array_append_val (self->attrib_locations, attrib);
+}
+
+void
+gsk_gl_compiler_clear_attributes (GskGLCompiler *self)
+{
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+
+  g_array_set_size (self->attrib_locations, 0);
+}
+
+void
+gsk_gl_compiler_set_preamble (GskGLCompiler     *self,
+                              GskGLCompilerKind  kind,
+                              GBytes            *preamble_bytes)
+{
+  GBytes **loc = NULL;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (preamble_bytes != NULL);
+
+  if (kind == GSK_GL_COMPILER_ALL)
+    loc = &self->all_preamble;
+  else if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_preamble;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_preamble;
+  else
+    g_return_if_reached ();
+
+  g_assert (loc != NULL);
+
+  if (*loc != preamble_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = preamble_bytes ? g_bytes_ref (preamble_bytes) : NULL;
+    }
+}
+
+void
+gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler     *self,
+                                            GskGLCompilerKind  kind,
+                                            const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set shader from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_preamble (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+void
+gsk_gl_compiler_set_source (GskGLCompiler     *self,
+                            GskGLCompilerKind  kind,
+                            GBytes            *source_bytes)
+{
+  GBytes **loc = NULL;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+
+  if (source_bytes == NULL)
+    source_bytes = empty_bytes;
+
+  /* If kind is ALL, then we need to split the fragment and
+   * vertex shaders from the bytes and assign them individually.
+   * This safely scans for FRAGMENT_SHADER and VERTEX_SHADER as
+   * specified within the GLSL resources. Some care is taken to
+   * use GBytes which reference the original bytes instead of
+   * copying them.
+   */
+  if (kind == GSK_GL_COMPILER_ALL)
+    {
+      gsize len = 0;
+      const char *source;
+      const char *vertex_shader_start;
+      const char *fragment_shader_start;
+      const char *endpos;
+      GBytes *fragment_bytes;
+      GBytes *vertex_bytes;
+
+      g_clear_pointer (&self->fragment_source, g_bytes_unref);
+      g_clear_pointer (&self->vertex_source, g_bytes_unref);
+
+      source = g_bytes_get_data (source_bytes, &len);
+      endpos = source + len;
+      vertex_shader_start = g_strstr_len (source, len, "VERTEX_SHADER");
+      fragment_shader_start = g_strstr_len (source, len, "FRAGMENT_SHADER");
+
+      if (vertex_shader_start == NULL)
+        {
+          g_warning ("Failed to locate VERTEX_SHADER in shader source");
+          return;
+        }
+
+      if (fragment_shader_start == NULL)
+        {
+          g_warning ("Failed to locate FRAGMENT_SHADER in shader source");
+          return;
+        }
+
+      if (vertex_shader_start > fragment_shader_start)
+        {
+          g_warning ("VERTEX_SHADER must come before FRAGMENT_SHADER");
+          return;
+        }
+
+      /* Locate next newlines */
+      while (vertex_shader_start < endpos && vertex_shader_start[0] != '\n')
+        vertex_shader_start++;
+      while (fragment_shader_start < endpos && fragment_shader_start[0] != '\n')
+        fragment_shader_start++;
+
+      vertex_bytes = g_bytes_new_from_bytes (source_bytes,
+                                             vertex_shader_start - source,
+                                             fragment_shader_start - vertex_shader_start);
+      fragment_bytes = g_bytes_new_from_bytes (source_bytes,
+                                               fragment_shader_start - source,
+                                               endpos - fragment_shader_start);
+
+      gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_VERTEX, vertex_bytes);
+      gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_FRAGMENT, fragment_bytes);
+
+      g_bytes_unref (fragment_bytes);
+      g_bytes_unref (vertex_bytes);
+
+      return;
+    }
+
+  if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_source;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_source;
+  else
+    g_return_if_reached ();
+
+  if (*loc != source_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = g_bytes_ref (source_bytes);
+    }
+}
+
+void
+gsk_gl_compiler_set_source_from_resource (GskGLCompiler     *self,
+                                          GskGLCompilerKind  kind,
+                                          const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set shader from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_source (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+void
+gsk_gl_compiler_set_suffix (GskGLCompiler     *self,
+                            GskGLCompilerKind  kind,
+                            GBytes            *suffix_bytes)
+{
+  GBytes **loc;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (suffix_bytes != NULL);
+
+  if (suffix_bytes == NULL)
+    suffix_bytes = empty_bytes;
+
+  if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_suffix;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_suffix;
+  else
+    g_return_if_reached ();
+
+  if (*loc != suffix_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = g_bytes_ref (suffix_bytes);
+    }
+}
+
+void
+gsk_gl_compiler_set_suffix_from_resource (GskGLCompiler     *self,
+                                          GskGLCompilerKind  kind,
+                                          const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set suffix from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_suffix (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+static void
+prepend_line_numbers (char    *code,
+                      GString *s)
+{
+  char *p;
+  int line;
+
+  p = code;
+  line = 1;
+  while (*p)
+    {
+      char *end = strchr (p, '\n');
+      if (end)
+        end = end + 1; /* Include newline */
+      else
+        end = p + strlen (p);
+
+      g_string_append_printf (s, "%3d| ", line++);
+      g_string_append_len (s, p, end - p);
+
+      p = end;
+    }
+}
+
+static gboolean
+check_shader_error (int      shader_id,
+                    GError **error)
+{
+  GLint status;
+  GLint log_len;
+  GLint code_len;
+  char *buffer;
+  char *code;
+  GString *s;
+
+  glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status);
+
+  if G_LIKELY (status == GL_TRUE)
+    return TRUE;
+
+  glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len);
+  buffer = g_malloc0 (log_len + 1);
+  glGetShaderInfoLog (shader_id, log_len, NULL, buffer);
+
+  glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
+  code = g_malloc0 (code_len + 1);
+  glGetShaderSource (shader_id, code_len, NULL, code);
+
+  s = g_string_new ("");
+  prepend_line_numbers (code, s);
+
+  g_set_error (error,
+               GDK_GL_ERROR,
+               GDK_GL_ERROR_COMPILATION_FAILED,
+               "Compilation failure in shader.\n"
+               "Source Code: %s\n"
+               "\n"
+               "Error Message:\n"
+               "%s\n"
+               "\n",
+               s->str,
+               buffer);
+
+  g_string_free (s, TRUE);
+  g_free (buffer);
+  g_free (code);
+
+  return FALSE;
+}
+
+static void
+print_shader_info (const char *prefix,
+                   int         shader_id,
+                   const char *name)
+{
+  if (GSK_DEBUG_CHECK(SHADERS))
+    {
+      int code_len;
+
+      glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
+
+      if (code_len > 0)
+        {
+          char *code;
+          GString *s;
+
+          code = g_malloc0 (code_len + 1);
+          glGetShaderSource (shader_id, code_len, NULL, code);
+
+          s = g_string_new (NULL);
+          prepend_line_numbers (code, s);
+
+          g_message ("%s %d, %s:\n%s",
+                     prefix, shader_id,
+                     name ? name : "unnamed",
+                     s->str);
+          g_string_free (s,  TRUE);
+          g_free (code);
+        }
+    }
+}
+
+static const char *
+get_shader_string (GBytes *bytes)
+{
+  /* 0 length bytes will give us NULL back */
+  const char *str = g_bytes_get_data (bytes, NULL);
+  return str ? str : "";
+}
+
+GskGLProgram *
+gsk_gl_compiler_compile (GskGLCompiler  *self,
+                         const char     *name,
+                         const char     *clip,
+                         GError        **error)
+{
+  char version[32];
+  const char *debug = "";
+  const char *legacy = "";
+  const char *gl3 = "";
+  const char *gles = "";
+  int program_id;
+  int vertex_id;
+  int fragment_id;
+  int status;
+
+  g_return_val_if_fail (GSK_IS_GL_COMPILER (self), NULL);
+  g_return_val_if_fail (self->all_preamble != NULL, NULL);
+  g_return_val_if_fail (self->fragment_preamble != NULL, NULL);
+  g_return_val_if_fail (self->vertex_preamble != NULL, NULL);
+  g_return_val_if_fail (self->fragment_source != NULL, NULL);
+  g_return_val_if_fail (self->vertex_source != NULL, NULL);
+  g_return_val_if_fail (self->driver != NULL, NULL);
+
+  gsk_gl_command_queue_make_current (self->driver->command_queue);
+
+  g_snprintf (version, sizeof version, "#version %d\n", self->glsl_version);
+
+  if (self->debug_shaders)
+    debug = "#define GSK_DEBUG 1\n";
+
+  if (self->legacy)
+    legacy = "#define GSK_LEGACY 1\n";
+
+  if (self->gles)
+    gles = "#define GSK_GLES 1\n";
+
+  if (self->gl3)
+    gl3 = "#define GSK_GL3 1\n";
+
+  vertex_id = glCreateShader (GL_VERTEX_SHADER);
+  glShaderSource (vertex_id,
+                  10,
+                  (const char *[]) {
+                    version, debug, legacy, gl3, gles,
+                    clip,
+                    get_shader_string (self->all_preamble),
+                    get_shader_string (self->vertex_preamble),
+                    get_shader_string (self->vertex_source),
+                    get_shader_string (self->vertex_suffix),
+                  },
+                  (int[]) {
+                    strlen (version),
+                    strlen (debug),
+                    strlen (legacy),
+                    strlen (gl3),
+                    strlen (gles),
+                    strlen (clip),
+                    g_bytes_get_size (self->all_preamble),
+                    g_bytes_get_size (self->vertex_preamble),
+                    g_bytes_get_size (self->vertex_source),
+                    g_bytes_get_size (self->vertex_suffix),
+                  });
+  glCompileShader (vertex_id);
+
+  if (!check_shader_error (vertex_id, error))
+    {
+      glDeleteShader (vertex_id);
+      return NULL;
+    }
+
+  print_shader_info ("Vertex shader", vertex_id, name);
+
+  fragment_id = glCreateShader (GL_FRAGMENT_SHADER);
+  glShaderSource (fragment_id,
+                  10,
+                  (const char *[]) {
+                    version, debug, legacy, gl3, gles,
+                    clip,
+                    get_shader_string (self->all_preamble),
+                    get_shader_string (self->fragment_preamble),
+                    get_shader_string (self->fragment_source),
+                    get_shader_string (self->fragment_suffix),
+                  },
+                  (int[]) {
+                    strlen (version),
+                    strlen (debug),
+                    strlen (legacy),
+                    strlen (gl3),
+                    strlen (gles),
+                    strlen (clip),
+                    g_bytes_get_size (self->all_preamble),
+                    g_bytes_get_size (self->fragment_preamble),
+                    g_bytes_get_size (self->fragment_source),
+                    g_bytes_get_size (self->fragment_suffix),
+                  });
+  glCompileShader (fragment_id);
+
+  if (!check_shader_error (fragment_id, error))
+    {
+      glDeleteShader (vertex_id);
+      glDeleteShader (fragment_id);
+      return NULL;
+    }
+
+  print_shader_info ("Fragment shader", fragment_id, name);
+
+  program_id = glCreateProgram ();
+  glAttachShader (program_id, vertex_id);
+  glAttachShader (program_id, fragment_id);
+
+  for (guint i = 0; i < self->attrib_locations->len; i++)
+    {
+      const GskGLProgramAttrib *attrib;
+
+      attrib = &g_array_index (self->attrib_locations, GskGLProgramAttrib, i);
+      glBindAttribLocation (program_id, attrib->location, attrib->name);
+    }
+
+  glLinkProgram (program_id);
+
+  glGetProgramiv (program_id, GL_LINK_STATUS, &status);
+
+  glDetachShader (program_id, vertex_id);
+  glDeleteShader (vertex_id);
+
+  glDetachShader (program_id, fragment_id);
+  glDeleteShader (fragment_id);
+
+  if (status == GL_FALSE)
+    {
+      char *buffer = NULL;
+      int log_len = 0;
+
+      glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len);
+
+      if (log_len > 0)
+        {
+          /* log_len includes NULL */
+          buffer = g_malloc0 (log_len);
+          glGetProgramInfoLog (program_id, log_len, NULL, buffer);
+        }
+
+      g_warning ("Linking failure in shader:\n%s",
+                 buffer ? buffer : "");
+
+      g_set_error (error,
+                   GDK_GL_ERROR,
+                   GDK_GL_ERROR_LINK_FAILED,
+                   "Linking failure in shader: %s",
+                   buffer ? buffer : "");
+
+      g_free (buffer);
+
+      glDeleteProgram (program_id);
+
+      return NULL;
+    }
+
+  return gsk_gl_program_new (self->driver, name, program_id);
+}
diff --git a/gsk/gl/gskglcompilerprivate.h b/gsk/gl/gskglcompilerprivate.h
new file mode 100644 (file)
index 0000000..54f3e00
--- /dev/null
@@ -0,0 +1,70 @@
+/* gskglcompilerprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_COMPILER_PRIVATE_H__
+#define __GSK_GL_COMPILER_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef enum _GskGLCompilerKind
+{
+  GSK_GL_COMPILER_ALL,
+  GSK_GL_COMPILER_FRAGMENT,
+  GSK_GL_COMPILER_VERTEX,
+} GskGLCompilerKind;
+
+#define GSK_TYPE_GL_COMPILER (gsk_gl_compiler_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLCompiler, gsk_gl_compiler, GSK, GL_COMPILER, GObject)
+
+GskGLCompiler * gsk_gl_compiler_new                        (GskGLDriver        *driver,
+                                                            gboolean            debug);
+void            gsk_gl_compiler_set_preamble               (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            GBytes             *preamble_bytes);
+void            gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            const char         *resource_path);
+void            gsk_gl_compiler_set_source                 (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            GBytes             *source_bytes);
+void            gsk_gl_compiler_set_source_from_resource   (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            const char         *resource_path);
+void            gsk_gl_compiler_set_suffix                 (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            GBytes             *suffix_bytes);
+void            gsk_gl_compiler_set_suffix_from_resource   (GskGLCompiler      *self,
+                                                            GskGLCompilerKind   kind,
+                                                            const char         *resource_path);
+void            gsk_gl_compiler_bind_attribute             (GskGLCompiler      *self,
+                                                            const char         *name,
+                                                            guint               location);
+void            gsk_gl_compiler_clear_attributes           (GskGLCompiler      *self);
+GskGLProgram  * gsk_gl_compiler_compile                    (GskGLCompiler      *self,
+                                                            const char         *name,
+                                                            const char         *clip,
+                                                            GError            **error);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_COMPILER_PRIVATE_H__ */
diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c
new file mode 100644 (file)
index 0000000..a19522c
--- /dev/null
@@ -0,0 +1,1386 @@
+/* gskgldriver.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkdisplayprivate.h>
+#include <gdk/gdktextureprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskglshaderprivate.h>
+#include <gsk/gskrendererprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskglcompilerprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglglyphlibraryprivate.h"
+#include "gskgliconlibraryprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskglshadowlibraryprivate.h"
+#include "gskgltextureprivate.h"
+#include "fp16private.h"
+
+#define ATLAS_SIZE 512
+#define MAX_OLD_RATIO 0.5
+
+G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT)
+
+static guint
+texture_key_hash (gconstpointer v)
+{
+  const GskTextureKey *k = (const GskTextureKey *)v;
+
+  /* Optimize for 0..3 where 0 is the scaled out case. Usually
+   * we'll be squarely on 1 or 2 for standard vs HiDPI. When rendering
+   * to a texture scaled out like in node-editor, we might be < 1.
+   */
+  guint scale_x = floorf (k->scale_x);
+  guint scale_y = floorf (k->scale_y);
+
+  return GPOINTER_TO_SIZE (k->pointer) ^
+    ((scale_x << 8) |
+     (scale_y << 6) |
+     (k->filter << 1) |
+     k->pointer_is_child);
+}
+
+static gboolean
+texture_key_equal (gconstpointer v1,
+                   gconstpointer v2)
+{
+  const GskTextureKey *k1 = (const GskTextureKey *)v1;
+  const GskTextureKey *k2 = (const GskTextureKey *)v2;
+
+  return k1->pointer == k2->pointer &&
+         k1->scale_x == k2->scale_x &&
+         k1->scale_y == k2->scale_y &&
+         k1->filter == k2->filter &&
+         k1->pointer_is_child == k2->pointer_is_child &&
+         (!k1->pointer_is_child || memcmp (&k1->parent_rect, &k2->parent_rect, sizeof k1->parent_rect) == 0);
+}
+
+static void
+remove_texture_key_for_id (GskGLDriver *self,
+                           guint        texture_id)
+{
+  GskTextureKey *key;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (texture_id > 0);
+
+  /* g_hash_table_remove() will cause @key to be freed */
+  if (g_hash_table_steal_extended (self->texture_id_to_key,
+                                   GUINT_TO_POINTER (texture_id),
+                                   NULL,
+                                   (gpointer *)&key))
+    g_hash_table_remove (self->key_to_texture_id, key);
+}
+
+static void
+gsk_gl_texture_destroyed (gpointer data)
+{
+  ((GskGLTexture *)data)->user = NULL;
+}
+
+static void
+gsk_gl_driver_autorelease_texture (GskGLDriver *self,
+                                   guint        texture_id)
+{
+  g_assert (GSK_IS_GL_DRIVER (self));
+
+  g_array_append_val (self->texture_pool, texture_id);
+}
+
+static guint
+gsk_gl_driver_collect_unused_textures (GskGLDriver *self,
+                                       gint64       watermark)
+{
+  GHashTableIter iter;
+  gpointer k, v;
+  guint old_size;
+  guint collected;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+
+  old_size = g_hash_table_size (self->textures);
+
+  g_hash_table_iter_init (&iter, self->textures);
+  while (g_hash_table_iter_next (&iter, &k, &v))
+    {
+      GskGLTexture *t = v;
+
+      if (t->user || t->permanent)
+        continue;
+
+      if (t->last_used_in_frame <= watermark)
+        {
+          g_hash_table_iter_steal (&iter);
+
+          g_assert (t->link.prev == NULL);
+          g_assert (t->link.next == NULL);
+          g_assert (t->link.data == t);
+
+          remove_texture_key_for_id (self, t->texture_id);
+          gsk_gl_driver_autorelease_texture (self, t->texture_id);
+          t->texture_id = 0;
+          gsk_gl_texture_free (t);
+        }
+    }
+
+  collected = old_size - g_hash_table_size (self->textures);
+
+  return collected;
+}
+
+static void
+gsk_gl_texture_atlas_free (GskGLTextureAtlas *atlas)
+{
+  if (atlas->texture_id != 0)
+    {
+      glDeleteTextures (1, &atlas->texture_id);
+      atlas->texture_id = 0;
+    }
+
+  g_clear_pointer (&atlas->nodes, g_free);
+  g_slice_free (GskGLTextureAtlas, atlas);
+}
+
+GskGLTextureAtlas *
+gsk_gl_driver_create_atlas (GskGLDriver *self)
+{
+  GskGLTextureAtlas *atlas;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+
+  atlas = g_slice_new0 (GskGLTextureAtlas);
+  atlas->width = ATLAS_SIZE;
+  atlas->height = ATLAS_SIZE;
+  /* TODO: We might want to change the strategy about the amount of
+   *       nodes here? stb_rect_pack.h says width is optimal. */
+  atlas->nodes = g_malloc0_n (atlas->width, sizeof (struct stbrp_node));
+  stbrp_init_target (&atlas->context, atlas->width, atlas->height, atlas->nodes, atlas->width);
+  atlas->texture_id = gsk_gl_command_queue_create_texture (self->command_queue,
+                                                           atlas->width,
+                                                           atlas->height,
+                                                           GL_RGBA8,
+                                                           GL_LINEAR,
+                                                           GL_LINEAR);
+
+  gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
+                                      GL_TEXTURE, atlas->texture_id,
+                                      "Texture atlas %d",
+                                      atlas->texture_id);
+
+  g_ptr_array_add (self->atlases, atlas);
+
+  return atlas;
+}
+
+static void
+remove_program (gpointer data)
+{
+  GskGLProgram *program = data;
+
+  g_assert (!program || GSK_IS_GL_PROGRAM (program));
+
+  if (program != NULL)
+    {
+      gsk_gl_program_delete (program);
+      g_object_unref (program);
+    }
+}
+
+static void
+gsk_gl_driver_shader_weak_cb (gpointer  data,
+                              GObject  *where_object_was)
+{
+  GskGLDriver *self = data;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+
+  if (self->shader_cache != NULL)
+    g_hash_table_remove (self->shader_cache, where_object_was);
+}
+
+static void
+gsk_gl_driver_dispose (GObject *object)
+{
+  GskGLDriver *self = (GskGLDriver *)object;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (self->in_frame == FALSE);
+
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \
+  GSK_GL_DELETE_PROGRAM(name);                          \
+  GSK_GL_DELETE_PROGRAM(name ## _no_clip);              \
+  GSK_GL_DELETE_PROGRAM(name ## _rect_clip);
+#define GSK_GL_DELETE_PROGRAM(name)                     \
+  G_STMT_START {                                        \
+    if (self->name)                                     \
+      gsk_gl_program_delete (self->name);               \
+    g_clear_object (&self->name);                       \
+  } G_STMT_END;
+# include "gskglprograms.defs"
+#undef GSK_GL_NO_UNIFORMS
+#undef GSK_GL_ADD_UNIFORM
+#undef GSK_GL_DEFINE_PROGRAM
+
+  if (self->shader_cache != NULL)
+    {
+      GHashTableIter iter;
+      gpointer k, v;
+
+      g_hash_table_iter_init (&iter, self->shader_cache);
+      while (g_hash_table_iter_next (&iter, &k, &v))
+        {
+          GskGLShader *shader = k;
+          g_object_weak_unref (G_OBJECT (shader),
+                               gsk_gl_driver_shader_weak_cb,
+                               self);
+          g_hash_table_iter_remove (&iter);
+        }
+
+      g_clear_pointer (&self->shader_cache, g_hash_table_unref);
+    }
+
+  if (self->command_queue != NULL)
+    {
+      gsk_gl_command_queue_make_current (self->command_queue);
+      gsk_gl_driver_collect_unused_textures (self, 0);
+      g_clear_object (&self->command_queue);
+    }
+
+  if (self->autorelease_framebuffers->len > 0)
+    {
+      glDeleteFramebuffers (self->autorelease_framebuffers->len,
+                            (GLuint *)(gpointer)self->autorelease_framebuffers->data);
+      self->autorelease_framebuffers->len = 0;
+    }
+
+  g_clear_pointer (&self->texture_pool, g_array_unref);
+
+  g_assert (!self->textures || g_hash_table_size (self->textures) == 0);
+  g_assert (!self->texture_id_to_key || g_hash_table_size (self->texture_id_to_key) == 0);
+  g_assert (!self->key_to_texture_id|| g_hash_table_size (self->key_to_texture_id) == 0);
+
+  g_clear_object (&self->glyphs);
+  g_clear_object (&self->icons);
+  g_clear_object (&self->shadows);
+
+  g_clear_pointer (&self->atlases, g_ptr_array_unref);
+  g_clear_pointer (&self->autorelease_framebuffers, g_array_unref);
+  g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
+  g_clear_pointer (&self->textures, g_hash_table_unref);
+  g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
+  g_clear_pointer (&self->texture_id_to_key, g_hash_table_unref);
+  g_clear_pointer (&self->render_targets, g_ptr_array_unref);
+  g_clear_pointer (&self->shader_cache, g_hash_table_unref);
+
+  g_clear_object (&self->command_queue);
+  g_clear_object (&self->shared_command_queue);
+
+  G_OBJECT_CLASS (gsk_gl_driver_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_driver_class_init (GskGLDriverClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_gl_driver_dispose;
+}
+
+static void
+gsk_gl_driver_init (GskGLDriver *self)
+{
+  self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (guint));
+  self->textures = g_hash_table_new_full (NULL, NULL, NULL,
+                                          (GDestroyNotify)gsk_gl_texture_free);
+  self->texture_id_to_key = g_hash_table_new (NULL, NULL);
+  self->key_to_texture_id = g_hash_table_new_full (texture_key_hash,
+                                                   texture_key_equal,
+                                                   g_free,
+                                                   NULL);
+  self->shader_cache = g_hash_table_new_full (NULL, NULL, NULL, remove_program);
+  self->texture_pool = g_array_new (FALSE, FALSE, sizeof (guint));
+  self->render_targets = g_ptr_array_new ();
+  self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_gl_texture_atlas_free);
+}
+
+static gboolean
+gsk_gl_driver_load_programs (GskGLDriver  *self,
+                             GError      **error)
+{
+  GskGLCompiler *compiler;
+  gboolean ret = FALSE;
+  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self->command_queue));
+
+  compiler = gsk_gl_compiler_new (self, self->debug);
+
+  /* Setup preambles that are shared by all shaders */
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                               GSK_GL_COMPILER_ALL,
+                                               "/org/gtk/libgsk/gl/preamble.glsl");
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                               GSK_GL_COMPILER_VERTEX,
+                                               "/org/gtk/libgsk/gl/preamble.vs.glsl");
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                               GSK_GL_COMPILER_FRAGMENT,
+                                               "/org/gtk/libgsk/gl/preamble.fs.glsl");
+
+  /* Setup attributes that are provided via VBO */
+  gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0);
+  gsk_gl_compiler_bind_attribute (compiler, "aUv", 1);
+  gsk_gl_compiler_bind_attribute (compiler, "aColor", 2);
+  gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3);
+
+  /* Use XMacros to register all of our programs and their uniforms */
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)                                                      \
+  gsk_gl_program_add_uniform (program, #name, UNIFORM_##KEY);
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms)                                         \
+  gsk_gl_compiler_set_source_from_resource (compiler, GSK_GL_COMPILER_ALL, resource);           \
+  GSK_GL_COMPILE_PROGRAM(name ## _no_clip, uniforms, "#define NO_CLIP 1\n");                    \
+  GSK_GL_COMPILE_PROGRAM(name ## _rect_clip, uniforms, "#define RECT_CLIP 1\n");                \
+  GSK_GL_COMPILE_PROGRAM(name, uniforms, "");
+#define GSK_GL_COMPILE_PROGRAM(name, uniforms, clip)                                            \
+  G_STMT_START {                                                                                \
+    GskGLProgram *program;                                                                      \
+    gboolean have_alpha;                                                                        \
+    gboolean have_source;                                                                       \
+                                                                                                \
+    if (!(program = gsk_gl_compiler_compile (compiler, #name, clip, error)))                    \
+      goto failure;                                                                             \
+                                                                                                \
+    have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);         \
+    have_source = gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);      \
+    gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);              \
+    gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);                \
+    gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);            \
+    gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);              \
+                                                                                                \
+    uniforms                                                                                    \
+                                                                                                \
+    gsk_gl_program_uniforms_added (program, have_source);                                       \
+    if (have_alpha)                                                                             \
+      gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f);                    \
+                                                                                                \
+    *(GskGLProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskGLDriver, name)) =                \
+         g_steal_pointer (&program);                                                            \
+  } G_STMT_END;
+# include "gskglprograms.defs"
+#undef GSK_GL_DEFINE_PROGRAM_CLIP
+#undef GSK_GL_DEFINE_PROGRAM
+#undef GSK_GL_ADD_UNIFORM
+
+  ret = TRUE;
+
+failure:
+  g_clear_object (&compiler);
+
+  gdk_profiler_end_mark (start_time, "load programs", NULL);
+
+  return ret;
+}
+
+/**
+ * gsk_gl_driver_autorelease_framebuffer:
+ * @self: a `GskGLDriver`
+ * @framebuffer_id: the id of the OpenGL framebuffer
+ *
+ * Marks @framebuffer_id to be deleted when the current frame has cmopleted.
+ */
+static void
+gsk_gl_driver_autorelease_framebuffer (GskGLDriver *self,
+                                       guint        framebuffer_id)
+{
+  g_assert (GSK_IS_GL_DRIVER (self));
+
+  g_array_append_val (self->autorelease_framebuffers, framebuffer_id);
+}
+
+static GskGLDriver *
+gsk_gl_driver_new (GskGLCommandQueue  *command_queue,
+                   gboolean            debug_shaders,
+                   GError            **error)
+{
+  GskGLDriver *self;
+  GdkGLContext *context;
+  gint64 before G_GNUC_UNUSED;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL);
+
+  before = GDK_PROFILER_CURRENT_TIME;
+
+  context = gsk_gl_command_queue_get_context (command_queue);
+
+  gdk_gl_context_make_current (context);
+
+  self = g_object_new (GSK_TYPE_GL_DRIVER, NULL);
+  self->command_queue = g_object_ref (command_queue);
+  self->shared_command_queue = g_object_ref (command_queue);
+  self->debug = !!debug_shaders;
+
+  if (!gsk_gl_driver_load_programs (self, error))
+    {
+      g_object_unref (self);
+      return NULL;
+    }
+
+  self->glyphs = gsk_gl_glyph_library_new (self);
+  self->icons = gsk_gl_icon_library_new (self);
+  self->shadows = gsk_gl_shadow_library_new (self);
+
+  gdk_profiler_end_mark (before, "create GskGLDriver", NULL);
+
+  return g_steal_pointer (&self);
+}
+
+/**
+ * gsk_gl_driver_for_display:
+ * @display: A #GdkDisplay that is known to support GL
+ * @debug_shaders: if debug information for shaders should be displayed
+ * @error: location for error information
+ *
+ * Retrieves a driver for a shared display. Generally this is shared across all GL
+ * contexts for a display so that fewer programs are necessary for driving output.
+ *
+ * Returns: (transfer full): a `GskGLDriver` if successful; otherwise %NULL and
+ *   @error is set.
+ */
+GskGLDriver *
+gsk_gl_driver_for_display (GdkDisplay  *display,
+                           gboolean     debug_shaders,
+                           GError     **error)
+{
+  GdkGLContext *context;
+  GskGLCommandQueue *command_queue = NULL;
+  GskGLDriver *driver;
+
+  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
+
+  if ((driver = g_object_get_data (G_OBJECT (display), "GSK_GL_DRIVER")))
+    return g_object_ref (driver);
+
+  context = gdk_display_get_gl_context (display);
+  g_assert (context);
+
+  gdk_gl_context_make_current (context);
+
+  /* Initially we create a command queue using the shared context. However,
+   * as frames are processed this will be replaced with the command queue
+   * for a given renderer. But since the programs are compiled into the
+   * shared context, all other contexts sharing with it will have access
+   * to those programs.
+   */
+  command_queue = gsk_gl_command_queue_new (context, NULL);
+
+  if (!(driver = gsk_gl_driver_new (command_queue, debug_shaders, error)))
+    goto failure;
+
+  g_object_set_data_full (G_OBJECT (display),
+                          "GSK_GL_DRIVER",
+                          g_object_ref (driver),
+                          g_object_unref);
+
+failure:
+  g_clear_object (&command_queue);
+
+  return g_steal_pointer (&driver);
+}
+
+static GPtrArray *
+gsk_gl_driver_compact_atlases (GskGLDriver *self)
+{
+  GPtrArray *removed = NULL;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+
+  for (guint i = self->atlases->len; i > 0; i--)
+    {
+      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1);
+
+      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
+        {
+          GSK_NOTE (GLYPH_CACHE,
+                    g_message ("Dropping atlas %d (%g.2%% old)", i,
+                               100.0 * gsk_gl_texture_atlas_get_unused_ratio (atlas)));
+          if (removed == NULL)
+            removed = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_gl_texture_atlas_free);
+          g_ptr_array_add (removed, g_ptr_array_steal_index (self->atlases, i - 1));
+        }
+    }
+
+  GSK_NOTE (GLYPH_CACHE, {
+    static guint timestamp;
+    if (timestamp++ % 60 == 0)
+      g_message ("%d atlases", self->atlases->len);
+  });
+
+  return removed;
+}
+
+/**
+ * gsk_gl_driver_begin_frame:
+ * @self: a `GskGLDriver`
+ * @command_queue: A `GskGLCommandQueue` from the renderer
+ *
+ * Begin a new frame.
+ *
+ * Texture atlases, pools, and other resources will be prepared to draw the
+ * next frame. The command queue should be one that was created for the
+ * target context to be drawn into (the context of the renderer's surface).
+ */
+void
+gsk_gl_driver_begin_frame (GskGLDriver       *self,
+                           GskGLCommandQueue *command_queue)
+{
+  gint64 last_frame_id;
+  GPtrArray *removed;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue));
+  g_return_if_fail (self->in_frame == FALSE);
+
+  last_frame_id = self->current_frame_id;
+
+  self->in_frame = TRUE;
+  self->current_frame_id++;
+
+  g_set_object (&self->command_queue, command_queue);
+
+  gsk_gl_command_queue_begin_frame (self->command_queue);
+
+  /* Compact atlases with too many freed pixels */
+  removed = gsk_gl_driver_compact_atlases (self);
+
+  /* Mark unused pixel regions of the atlases */
+  gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->icons),
+                                       self->current_frame_id,
+                                       removed);
+  gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs),
+                                       self->current_frame_id,
+                                       removed);
+
+  /* Cleanup old shadows */
+  gsk_gl_shadow_library_begin_frame (self->shadows);
+
+  /* Remove all textures that are from a previous frame or are no
+   * longer used by linked GdkTexture. We do this at the beginning
+   * of the following frame instead of the end so that we reduce chances
+   * we block on any resources while delivering our frames.
+   */
+  gsk_gl_driver_collect_unused_textures (self, last_frame_id - 1);
+
+  /* Now free atlas textures */
+  g_clear_pointer (&removed, g_ptr_array_unref);
+}
+
+/**
+ * gsk_gl_driver_end_frame:
+ * @self: a `GskGLDriver`
+ *
+ * Clean up resources from drawing the current frame.
+ *
+ * Temporary resources used while drawing will be released.
+ */
+void
+gsk_gl_driver_end_frame (GskGLDriver *self)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (self->in_frame == TRUE);
+
+  gsk_gl_command_queue_make_current (self->command_queue);
+  gsk_gl_command_queue_end_frame (self->command_queue);
+
+  self->in_frame = FALSE;
+}
+
+/**
+ * gsk_gl_driver_after_frame:
+ * @self: a `GskGLDriver`
+ *
+ * This function does post-frame cleanup operations.
+ *
+ * To reduce the chances of blocking on the driver it is performed
+ * after the frame has swapped buffers.
+ */
+void
+gsk_gl_driver_after_frame (GskGLDriver *self)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (self->in_frame == FALSE);
+
+  /* Release any render targets (possibly adding them to
+   * self->autorelease_framebuffers) so we can release the FBOs immediately
+   * afterwards.
+   */
+  while (self->render_targets->len > 0)
+    {
+      GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1);
+
+      gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
+      gsk_gl_driver_autorelease_texture (self, render_target->texture_id);
+      g_slice_free (GskGLRenderTarget, render_target);
+
+      self->render_targets->len--;
+    }
+
+  /* Now that we have collected render targets, release all the FBOs */
+  if (self->autorelease_framebuffers->len > 0)
+    {
+      glDeleteFramebuffers (self->autorelease_framebuffers->len,
+                            (GLuint *)(gpointer)self->autorelease_framebuffers->data);
+      self->autorelease_framebuffers->len = 0;
+    }
+
+  /* Release any cached textures we used during the frame */
+  if (self->texture_pool->len > 0)
+    {
+      glDeleteTextures (self->texture_pool->len,
+                        (GLuint *)(gpointer)self->texture_pool->data);
+      self->texture_pool->len = 0;
+    }
+
+  /* Reset command queue to our shared queue incase we have operations
+   * that need to be processed outside of a frame (such as callbacks
+   * from external systems such as GDK).
+   */
+  g_set_object (&self->command_queue, self->shared_command_queue);
+}
+
+GdkGLContext *
+gsk_gl_driver_get_context (GskGLDriver *self)
+{
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), NULL);
+
+  return gsk_gl_command_queue_get_context (self->command_queue);
+}
+
+/**
+ * gsk_gl_driver_cache_texture:
+ * @self: a `GskGLDriver`
+ * @key: the key for the texture
+ * @texture_id: the id of the texture to be cached
+ *
+ * Inserts @texture_id into the texture cache using @key.
+ *
+ * Textures can be looked up by @key after calling this function using
+ * gsk_gl_driver_lookup_texture().
+ *
+ * Textures that have not been used within a number of frames will be
+ * purged from the texture cache automatically.
+ */
+void
+gsk_gl_driver_cache_texture (GskGLDriver         *self,
+                             const GskTextureKey *key,
+                             guint                texture_id)
+{
+  GskTextureKey *k;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (key != NULL);
+  g_assert (texture_id > 0);
+  g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id)));
+
+  k = g_memdup (key, sizeof *key);
+
+  g_hash_table_insert (self->key_to_texture_id, k, GUINT_TO_POINTER (texture_id));
+  g_hash_table_insert (self->texture_id_to_key, GUINT_TO_POINTER (texture_id), k);
+}
+
+/**
+ * gsk_gl_driver_load_texture:
+ * @self: a `GdkTexture`
+ * @texture: a `GdkTexture`
+ * @min_filter: GL_NEAREST or GL_LINEAR
+ * @mag_filter: GL_NEAREST or GL_LINEAR
+ *
+ * Loads a `GdkTexture` by uploading the contents to the GPU when
+ * necessary. If @texture is a `GdkGLTexture`, it can be used without
+ * uploading contents to the GPU.
+ *
+ * If the texture has already been uploaded and not yet released
+ * from cache, this function returns that texture id without further
+ * work.
+ *
+ * If the texture has not been used for a number of frames, it will
+ * be removed from cache.
+ *
+ * There is no need to release the resulting texture identifier after
+ * using it. It will be released automatically.
+ *
+ * Returns: a texture identifier
+ */
+guint
+gsk_gl_driver_load_texture (GskGLDriver *self,
+                            GdkTexture  *texture,
+                            int          min_filter,
+                            int          mag_filter)
+{
+  GdkGLContext *context;
+  GdkTexture *downloaded_texture;
+  GskGLTexture *t;
+  guint texture_id;
+  int height;
+  int width;
+  int format;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0);
+  g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), 0);
+
+  context = self->command_queue->context;
+
+  format = GL_RGBA8;
+
+  if (GDK_IS_GL_TEXTURE (texture))
+    {
+      GdkGLTexture *gl_texture = (GdkGLTexture *) texture;
+      GdkGLContext *texture_context = gdk_gl_texture_get_context (gl_texture);
+
+      if (gdk_gl_context_is_shared (context, texture_context))
+        {
+          /* A GL texture from the same GL context is a simple task... */
+          return gdk_gl_texture_get_id (gl_texture);
+        }
+      else
+        {
+          downloaded_texture = gdk_texture_download_texture (texture);
+        }
+    }
+  else
+    {
+      if ((t = gdk_texture_get_render_data (texture, self)))
+        {
+          if (t->min_filter == min_filter && t->mag_filter == mag_filter)
+            return t->texture_id;
+        }
+
+      downloaded_texture = gdk_texture_download_texture (texture);
+    }
+
+  /* The download_texture() call may have switched the GL context. Make sure
+   * the right context is at work again. */
+  gdk_gl_context_make_current (context);
+
+  width = gdk_texture_get_width (texture);
+  height = gdk_texture_get_height (texture);
+  texture_id = gsk_gl_command_queue_upload_texture (self->command_queue,
+                                                     downloaded_texture,
+                                                     0,
+                                                     0,
+                                                     width,
+                                                     height,
+                                                     min_filter,
+                                                     mag_filter);
+
+  t = gsk_gl_texture_new (texture_id,
+                           width, height, format, min_filter, mag_filter,
+                           self->current_frame_id);
+
+  g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t);
+
+  if (gdk_texture_set_render_data (texture, self, t, gsk_gl_texture_destroyed))
+    t->user = texture;
+
+  gdk_gl_context_label_object_printf (context, GL_TEXTURE, t->texture_id,
+                                      "GdkTexture<%p> %d", texture, t->texture_id);
+
+  g_clear_object (&downloaded_texture);
+
+  return texture_id;
+}
+
+/**
+ * gsk_gl_driver_create_texture:
+ * @self: a `GskGLDriver`
+ * @width: the width of the texture
+ * @height: the height of the texture
+ * @format: format for the texture
+ * @min_filter: GL_NEAREST or GL_LINEAR
+ * @mag_filter: GL_NEAREST or GL_FILTER
+ *
+ * Creates a new texture immediately that can be used by the caller
+ * to upload data, map to a framebuffer, or other uses which may
+ * modify the texture immediately.
+ *
+ * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F.
+ *
+ * Use gsk_gl_driver_release_texture() to release this texture back into
+ * the pool so it may be reused later in the pipeline.
+ *
+ * Returns: a `GskGLTexture` which can be returned to the pool with
+ *   gsk_gl_driver_release_texture().
+ */
+GskGLTexture *
+gsk_gl_driver_create_texture (GskGLDriver *self,
+                              float        width,
+                              float        height,
+                              int          format,
+                              int          min_filter,
+                              int          mag_filter)
+{
+  GskGLTexture *texture;
+  guint texture_id;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+
+  texture_id = gsk_gl_command_queue_create_texture (self->command_queue,
+                                                     width, height,
+                                                     format,
+                                                     min_filter, mag_filter);
+  texture = gsk_gl_texture_new (texture_id,
+                                 width, height,
+                                 format,
+                                 min_filter, mag_filter,
+                                 self->current_frame_id);
+  g_hash_table_insert (self->textures,
+                       GUINT_TO_POINTER (texture->texture_id),
+                       texture);
+
+  return texture;
+}
+
+/**
+ * gsk_gl_driver_release_texture:
+ * @self: a `GskGLDriver`
+ * @texture: a `GskGLTexture`
+ *
+ * Releases @texture back into the pool so that it can be used later
+ * in the command stream by future batches. This helps reduce VRAM
+ * usage on the GPU.
+ *
+ * When the frame has completed, pooled textures will be released
+ * to free additional VRAM back to the system.
+ */
+void
+gsk_gl_driver_release_texture (GskGLDriver  *self,
+                               GskGLTexture *texture)
+{
+  guint texture_id;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (texture != NULL);
+
+  texture_id = texture->texture_id;
+  texture->texture_id = 0;
+  gsk_gl_texture_free (texture);
+
+  if (texture_id > 0)
+    remove_texture_key_for_id (self, texture_id);
+
+  g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
+  gsk_gl_driver_autorelease_texture (self, texture_id);
+}
+
+/**
+ * gsk_gl_driver_create_render_target:
+ * @self: a `GskGLDriver`
+ * @width: the width for the render target
+ * @height: the height for the render target
+ * @format: the format to use
+ * @min_filter: the min filter to use for the texture
+ * @mag_filter: the mag filter to use for the texture
+ * @out_render_target: (out): a location for the render target
+ *
+ * Creates a new render target which contains a framebuffer and a texture
+ * bound to that framebuffer of the size @width x @height and using the
+ * appropriate filters.
+ *
+ * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F.
+ *
+ * Use gsk_gl_driver_release_render_target() when you are finished with
+ * the render target to release it. You may steal the texture from the
+ * render target when releasing it.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @out_fbo_id and
+ *   @out_texture_id are undefined.
+ */
+gboolean
+gsk_gl_driver_create_render_target (GskGLDriver        *self,
+                                    int                 width,
+                                    int                 height,
+                                    int                 format,
+                                    int                 min_filter,
+                                    int                 mag_filter,
+                                    GskGLRenderTarget **out_render_target)
+{
+  guint framebuffer_id;
+  guint texture_id;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), FALSE);
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), FALSE);
+  g_return_val_if_fail (out_render_target != NULL, FALSE);
+
+#if 0
+  if (self->render_targets->len > 0)
+    {
+      for (guint i = self->render_targets->len; i > 0; i--)
+        {
+          GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1);
+
+          if (render_target->width == width &&
+              render_target->height == height &&
+              render_target->min_filter == min_filter &&
+              render_target->mag_filter == mag_filter)
+            {
+              *out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1);
+              return TRUE;
+            }
+        }
+    }
+#endif
+
+  if (gsk_gl_command_queue_create_render_target (self->command_queue,
+                                                  width, height,
+                                                  format,
+                                                  min_filter, mag_filter,
+                                                  &framebuffer_id, &texture_id))
+    {
+      GskGLRenderTarget *render_target;
+
+      render_target = g_slice_new0 (GskGLRenderTarget);
+      render_target->min_filter = min_filter;
+      render_target->mag_filter = mag_filter;
+      render_target->format = format;
+      render_target->width = width;
+      render_target->height = height;
+      render_target->framebuffer_id = framebuffer_id;
+      render_target->texture_id = texture_id;
+
+      *out_render_target = render_target;
+
+      return TRUE;
+    }
+
+  *out_render_target = NULL;
+
+  return FALSE;
+}
+
+/**
+ * gsk_gl_driver_release_render_target:
+ * @self: a `GskGLDriver`
+ * @render_target: a `GskGLRenderTarget` created with
+ *   gsk_gl_driver_create_render_target().
+ * @release_texture: if the texture should also be released
+ *
+ * Releases a render target that was previously created. An attempt may
+ * be made to cache the render target so that future creations of render
+ * targets are performed faster.
+ *
+ * If @release_texture is %FALSE, the backing texture id is returned and
+ * the framebuffer is released. Otherwise, both the texture and framebuffer
+ * are released or cached until the end of the frame.
+ *
+ * This may be called when building the render job as the texture or
+ * framebuffer will not be removed immediately.
+ *
+ * Returns: a texture id if @release_texture is %FALSE, otherwise zero.
+ */
+guint
+gsk_gl_driver_release_render_target (GskGLDriver       *self,
+                                     GskGLRenderTarget *render_target,
+                                     gboolean           release_texture)
+{
+  guint texture_id;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0);
+  g_return_val_if_fail (render_target != NULL, 0);
+
+  if (release_texture)
+    {
+      texture_id = 0;
+      g_ptr_array_add (self->render_targets, render_target);
+    }
+  else
+    {
+      GskGLTexture *texture;
+
+      texture_id = render_target->texture_id;
+
+      texture = gsk_gl_texture_new (render_target->texture_id,
+                                     render_target->width,
+                                     render_target->height,
+                                     render_target->format,
+                                     render_target->min_filter,
+                                     render_target->mag_filter,
+                                     self->current_frame_id);
+      g_hash_table_insert (self->textures,
+                           GUINT_TO_POINTER (texture_id),
+                           g_steal_pointer (&texture));
+
+      gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
+      g_slice_free (GskGLRenderTarget, render_target);
+
+    }
+
+  return texture_id;
+}
+
+/**
+ * gsk_gl_driver_lookup_shader:
+ * @self: a `GskGLDriver`
+ * @shader: the shader to lookup or load
+ * @error: a location for a `GError`
+ *
+ * Attepts to load @shader from the shader cache.
+ *
+ * If it has not been loaded, then it will compile the shader on demand.
+ *
+ * Returns: (nullable) (transfer none): a `GskGLShader`
+ */
+GskGLProgram *
+gsk_gl_driver_lookup_shader (GskGLDriver  *self,
+                             GskGLShader  *shader,
+                             GError      **error)
+{
+  GskGLProgram *program;
+
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (shader != NULL, NULL);
+
+  program = g_hash_table_lookup (self->shader_cache, shader);
+
+  if (program == NULL)
+    {
+      const GskGLUniform *uniforms;
+      GskGLCompiler *compiler;
+      GBytes *suffix;
+      int n_required_textures;
+      int n_uniforms;
+
+      uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
+      if (n_uniforms > GSK_GL_PROGRAM_MAX_CUSTOM_ARGS)
+        {
+          g_set_error (error,
+                       GDK_GL_ERROR,
+                       GDK_GL_ERROR_UNSUPPORTED_FORMAT,
+                       "Tried to use %d uniforms, while only %d is supported",
+                       n_uniforms,
+                       GSK_GL_PROGRAM_MAX_CUSTOM_ARGS);
+          return NULL;
+        }
+
+      n_required_textures = gsk_gl_shader_get_n_textures (shader);
+      if (n_required_textures > GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES)
+        {
+          g_set_error (error,
+                       GDK_GL_ERROR,
+                       GDK_GL_ERROR_UNSUPPORTED_FORMAT,
+                       "Tried to use %d textures, while only %d is supported",
+                       n_required_textures,
+                       GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES);
+          return NULL;
+        }
+
+      compiler = gsk_gl_compiler_new (self, FALSE);
+      suffix = gsk_gl_shader_get_source (shader);
+
+      gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                                  GSK_GL_COMPILER_ALL,
+                                                  "/org/gtk/libgsk/gl/preamble.glsl");
+      gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                                  GSK_GL_COMPILER_VERTEX,
+                                                  "/org/gtk/libgsk/gl/preamble.vs.glsl");
+      gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                                  GSK_GL_COMPILER_FRAGMENT,
+                                                  "/org/gtk/libgsk/gl/preamble.fs.glsl");
+      gsk_gl_compiler_set_source_from_resource (compiler,
+                                                GSK_GL_COMPILER_ALL,
+                                                "/org/gtk/libgsk/gl/custom.glsl");
+      gsk_gl_compiler_set_suffix (compiler, GSK_GL_COMPILER_FRAGMENT, suffix);
+
+      /* Setup attributes that are provided via VBO */
+      gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0);
+      gsk_gl_compiler_bind_attribute (compiler, "aUv", 1);
+      gsk_gl_compiler_bind_attribute (compiler, "aColor", 2);
+      gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3);
+
+      if ((program = gsk_gl_compiler_compile (compiler, NULL, "", error)))
+        {
+          gboolean have_alpha;
+
+          gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);
+          gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);
+          gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);
+          gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);
+          gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);
+          have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);
+
+          gsk_gl_program_add_uniform (program, "u_size", UNIFORM_CUSTOM_SIZE);
+          gsk_gl_program_add_uniform (program, "u_texture1", UNIFORM_CUSTOM_TEXTURE1);
+          gsk_gl_program_add_uniform (program, "u_texture2", UNIFORM_CUSTOM_TEXTURE2);
+          gsk_gl_program_add_uniform (program, "u_texture3", UNIFORM_CUSTOM_TEXTURE3);
+          gsk_gl_program_add_uniform (program, "u_texture4", UNIFORM_CUSTOM_TEXTURE4);
+
+          /* Custom arguments (max is 8) */
+          for (guint i = 0; i < n_uniforms; i++)
+            gsk_gl_program_add_uniform (program, uniforms[i].name, UNIFORM_CUSTOM_ARG0+i);
+
+          gsk_gl_program_uniforms_added (program, TRUE);
+
+          if (have_alpha)
+            gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f);
+
+          g_hash_table_insert (self->shader_cache, shader, program);
+          g_object_weak_ref (G_OBJECT (shader),
+                             gsk_gl_driver_shader_weak_cb,
+                             self);
+        }
+
+      g_object_unref (compiler);
+    }
+
+  return program;
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+write_atlas_to_png (GskGLDriver       *driver,
+                    GskGLTextureAtlas *atlas,
+                    const char        *filename)
+{
+  GdkTexture *texture;
+
+  texture = gdk_gl_texture_new (gsk_gl_driver_get_context (driver),
+                                atlas->texture_id,
+                                atlas->width, atlas->height,
+                                NULL, NULL);
+  gdk_texture_save_to_png (texture, filename);
+  g_object_unref (texture);
+}
+
+void
+gsk_gl_driver_save_atlases_to_png (GskGLDriver *self,
+                                   const char  *directory)
+{
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+
+  if (directory == NULL)
+    directory = ".";
+
+  for (guint i = 0; i < self->atlases->len; i++)
+    {
+      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
+      char *filename = g_strdup_printf ("%s%sframe-%d-atlas-%d.png",
+                                        directory,
+                                        G_DIR_SEPARATOR_S,
+                                        (int)self->current_frame_id,
+                                        atlas->texture_id);
+      write_atlas_to_png (self, atlas, filename);
+      g_free (filename);
+    }
+}
+#endif
+
+GskGLCommandQueue *
+gsk_gl_driver_create_command_queue (GskGLDriver *self,
+                                     GdkGLContext *context)
+{
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return gsk_gl_command_queue_new (context, self->shared_command_queue->uniforms);
+}
+
+void
+gsk_gl_driver_add_texture_slices (GskGLDriver        *self,
+                                  GdkTexture         *texture,
+                                  GskGLTextureSlice **out_slices,
+                                  guint              *out_n_slices)
+{
+  int max_texture_size;
+  GskGLTextureSlice *slices;
+  GskGLTexture *t;
+  guint n_slices;
+  guint cols;
+  guint rows;
+  int tex_width;
+  int tex_height;
+  int x = 0, y = 0;
+
+  g_assert (GSK_IS_GL_DRIVER (self));
+  g_assert (GDK_IS_TEXTURE (texture));
+  g_assert (out_slices != NULL);
+  g_assert (out_n_slices != NULL);
+
+  /* XXX: Too much? */
+  max_texture_size = self->command_queue->max_texture_size / 4;
+
+  tex_width = texture->width;
+  tex_height = texture->height;
+  cols = (texture->width / max_texture_size) + 1;
+  rows = (texture->height / max_texture_size) + 1;
+
+  if ((t = gdk_texture_get_render_data (texture, self)))
+    {
+      *out_slices = t->slices;
+      *out_n_slices = t->n_slices;
+      return;
+    }
+
+  n_slices = cols * rows;
+  slices = g_new0 (GskGLTextureSlice, n_slices);
+
+  for (guint col = 0; col < cols; col ++)
+    {
+      int slice_width = MIN (max_texture_size, texture->width - x);
+
+      for (guint row = 0; row < rows; row ++)
+        {
+          int slice_height = MIN (max_texture_size, texture->height - y);
+          int slice_index = (col * rows) + row;
+          guint texture_id;
+
+          texture_id = gsk_gl_command_queue_upload_texture (self->command_queue,
+                                                            texture,
+                                                            x, y,
+                                                            slice_width, slice_height,
+                                                            GL_NEAREST, GL_NEAREST);
+
+          slices[slice_index].rect.x = x;
+          slices[slice_index].rect.y = y;
+          slices[slice_index].rect.width = slice_width;
+          slices[slice_index].rect.height = slice_height;
+          slices[slice_index].texture_id = texture_id;
+
+          y += slice_height;
+        }
+
+      y = 0;
+      x += slice_width;
+    }
+
+  /* Allocate one Texture for the entire thing. */
+  t = gsk_gl_texture_new (0,
+                          tex_width, tex_height,
+                          GL_RGBA8,
+                          GL_NEAREST, GL_NEAREST,
+                          self->current_frame_id);
+
+  /* Use gsk_gl_texture_free() as destroy notify here since we are
+   * not inserting this GskGLTexture into self->textures!
+   */
+  gdk_texture_set_render_data (texture, self, t,
+                               (GDestroyNotify)gsk_gl_texture_free);
+
+  t->slices = *out_slices = slices;
+  t->n_slices = *out_n_slices = n_slices;
+}
+
+GskGLTexture *
+gsk_gl_driver_mark_texture_permanent (GskGLDriver *self,
+                                      guint        texture_id)
+{
+  GskGLTexture *t;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+  g_return_val_if_fail (texture_id > 0, NULL);
+
+  if ((t = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
+    t->permanent = TRUE;
+
+  return t;
+}
+
+void
+gsk_gl_driver_release_texture_by_id (GskGLDriver *self,
+                                     guint        texture_id)
+{
+  GskGLTexture *texture;
+
+  g_return_if_fail (GSK_IS_GL_DRIVER (self));
+  g_return_if_fail (texture_id > 0);
+
+  remove_texture_key_for_id (self, texture_id);
+
+  if ((texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
+    gsk_gl_driver_release_texture (self, texture);
+}
+
+typedef struct _GskGLTextureState
+{
+  GdkGLContext *context;
+  GLuint        texture_id;
+} GskGLTextureState;
+
+static void
+create_texture_from_texture_destroy (gpointer data)
+{
+  GskGLTextureState *state = data;
+
+  g_assert (state != NULL);
+  g_assert (GDK_IS_GL_CONTEXT (state->context));
+
+  gdk_gl_context_make_current (state->context);
+  glDeleteTextures (1, &state->texture_id);
+  g_clear_object (&state->context);
+  g_slice_free (GskGLTextureState, state);
+}
+
+GdkTexture *
+gsk_gl_driver_create_gdk_texture (GskGLDriver *self,
+                                  guint        texture_id)
+{
+  GskGLTextureState *state;
+  GskGLTexture *texture;
+  int width, height;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL);
+  g_return_val_if_fail (self->command_queue != NULL, NULL);
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (self->command_queue->context), NULL);
+  g_return_val_if_fail (texture_id > 0, NULL);
+  g_return_val_if_fail (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)), NULL);
+
+  /* We must be tracking this texture_id already to use it */
+  if (!(texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
+    g_return_val_if_reached (NULL);
+
+  state = g_slice_new0 (GskGLTextureState);
+  state->texture_id = texture_id;
+  state->context = g_object_ref (self->command_queue->context);
+
+  g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
+
+  width = texture->width;
+  height = texture->height;
+
+  texture->texture_id = 0;
+  gsk_gl_texture_free (texture);
+
+  return gdk_gl_texture_new (self->command_queue->context,
+                             texture_id,
+                             width,
+                             height,
+                             create_texture_from_texture_destroy,
+                             state);
+}
diff --git a/gsk/gl/gskgldriverprivate.h b/gsk/gl/gskgldriverprivate.h
new file mode 100644 (file)
index 0000000..4500962
--- /dev/null
@@ -0,0 +1,251 @@
+/* gskgldriverprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_DRIVER_PRIVATE_H__
+#define __GSK_GL_DRIVER_PRIVATE_H__
+
+#include <gdk/gdkgltextureprivate.h>
+
+#include "gskgltypesprivate.h"
+#include "gskgltextureprivate.h"
+
+G_BEGIN_DECLS
+
+enum {
+  UNIFORM_SHARED_ALPHA,
+  UNIFORM_SHARED_SOURCE,
+  UNIFORM_SHARED_CLIP_RECT,
+  UNIFORM_SHARED_VIEWPORT,
+  UNIFORM_SHARED_PROJECTION,
+  UNIFORM_SHARED_MODELVIEW,
+
+  UNIFORM_SHARED_LAST
+};
+
+enum {
+  UNIFORM_CUSTOM_SIZE = UNIFORM_SHARED_LAST,
+  UNIFORM_CUSTOM_TEXTURE1,
+  UNIFORM_CUSTOM_TEXTURE2,
+  UNIFORM_CUSTOM_TEXTURE3,
+  UNIFORM_CUSTOM_TEXTURE4,
+  UNIFORM_CUSTOM_ARG0,
+  UNIFORM_CUSTOM_ARG1,
+  UNIFORM_CUSTOM_ARG2,
+  UNIFORM_CUSTOM_ARG3,
+  UNIFORM_CUSTOM_ARG4,
+  UNIFORM_CUSTOM_ARG5,
+  UNIFORM_CUSTOM_ARG6,
+  UNIFORM_CUSTOM_ARG7,
+
+  UNIFORM_CUSTOM_LAST
+};
+
+typedef struct {
+  gconstpointer   pointer;
+  float           scale_x;
+  float           scale_y;
+  int             filter;
+  int             pointer_is_child;
+  graphene_rect_t parent_rect; /* Valid when pointer_is_child */
+} GskTextureKey;
+
+#define GSK_GL_NO_UNIFORMS CONCAT_EXPANDED(UNIFORM_INVALID_,__COUNTER__)
+#define CONCAT_EXPANDED(a,b) CONCAT_EXPANDED2(a,b)
+#define CONCAT_EXPANDED2(a,b) a##b
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos,
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms };
+# include "gskglprograms.defs"
+#undef GSK_GL_DEFINE_PROGRAM
+#undef GSK_GL_ADD_UNIFORM
+#undef GSK_GL_NO_UNIFORMS
+#undef CONCAT_EXPANDED
+#undef CONCAT_EXPANDED2
+
+#define GSK_TYPE_GL_DRIVER (gsk_gl_driver_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLDriver, gsk_gl_driver, GSK, GL_DRIVER, GObject)
+
+struct _GskGLRenderTarget
+{
+  guint framebuffer_id;
+  guint texture_id;
+  int min_filter;
+  int mag_filter;
+  int format;
+  int width;
+  int height;
+};
+
+struct _GskGLDriver
+{
+  GObject parent_instance;
+
+  GskGLCommandQueue *shared_command_queue;
+  GskGLCommandQueue *command_queue;
+
+  GskGLGlyphLibrary *glyphs;
+  GskGLIconLibrary *icons;
+  GskGLShadowLibrary *shadows;
+
+  GArray *texture_pool;
+  GHashTable *textures;
+  GHashTable *key_to_texture_id;
+  GHashTable *texture_id_to_key;
+
+  GPtrArray *atlases;
+
+  GHashTable *shader_cache;
+
+  GArray *autorelease_framebuffers;
+  GPtrArray *render_targets;
+
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \
+  GskGLProgram *name ## _no_clip; \
+  GskGLProgram *name ## _rect_clip; \
+  GskGLProgram *name;
+# include "gskglprograms.defs"
+#undef GSK_GL_NO_UNIFORMS
+#undef GSK_GL_ADD_UNIFORM
+#undef GSK_GL_DEFINE_PROGRAM
+
+  gint64 current_frame_id;
+
+  /* Used to reduce number of comparisons */
+  guint stamps[UNIFORM_SHARED_LAST];
+
+  guint debug : 1;
+  guint in_frame : 1;
+};
+
+GskGLDriver       * gsk_gl_driver_for_display            (GdkDisplay          *display,
+                                                          gboolean             debug_shaders,
+                                                          GError             **error);
+GskGLCommandQueue * gsk_gl_driver_create_command_queue   (GskGLDriver         *self,
+                                                          GdkGLContext        *context);
+GdkGLContext      * gsk_gl_driver_get_context            (GskGLDriver         *self);
+gboolean            gsk_gl_driver_create_render_target   (GskGLDriver         *self,
+                                                          int                  width,
+                                                          int                  height,
+                                                          int                  format,
+                                                          int                  min_filter,
+                                                          int                  mag_filter,
+                                                          GskGLRenderTarget  **render_target);
+guint               gsk_gl_driver_release_render_target  (GskGLDriver         *self,
+                                                          GskGLRenderTarget   *render_target,
+                                                          gboolean             release_texture);
+void                gsk_gl_driver_begin_frame            (GskGLDriver         *self,
+                                                          GskGLCommandQueue   *command_queue);
+void                gsk_gl_driver_end_frame              (GskGLDriver         *self);
+void                gsk_gl_driver_after_frame            (GskGLDriver         *self);
+GdkTexture        * gsk_gl_driver_create_gdk_texture     (GskGLDriver         *self,
+                                                          guint                texture_id);
+void                gsk_gl_driver_cache_texture          (GskGLDriver         *self,
+                                                          const GskTextureKey *key,
+                                                          guint                texture_id);
+guint               gsk_gl_driver_load_texture           (GskGLDriver         *self,
+                                                          GdkTexture          *texture,
+                                                          int                  min_filter,
+                                                          int                  mag_filter);
+GskGLTexture      * gsk_gl_driver_create_texture         (GskGLDriver         *self,
+                                                          float                width,
+                                                          float                height,
+                                                          int                  format,
+                                                          int                  min_filter,
+                                                          int                  mag_filter);
+void                gsk_gl_driver_release_texture        (GskGLDriver         *self,
+                                                          GskGLTexture        *texture);
+void                gsk_gl_driver_release_texture_by_id  (GskGLDriver         *self,
+                                                          guint                texture_id);
+GskGLTexture      * gsk_gl_driver_mark_texture_permanent (GskGLDriver         *self,
+                                                          guint                texture_id);
+void                gsk_gl_driver_add_texture_slices     (GskGLDriver         *self,
+                                                          GdkTexture          *texture,
+                                                          GskGLTextureSlice  **out_slices,
+                                                          guint               *out_n_slices);
+GskGLProgram      * gsk_gl_driver_lookup_shader          (GskGLDriver         *self,
+                                                          GskGLShader         *shader,
+                                                          GError             **error);
+GskGLTextureAtlas * gsk_gl_driver_create_atlas           (GskGLDriver         *self);
+
+#ifdef G_ENABLE_DEBUG
+void                gsk_gl_driver_save_atlases_to_png    (GskGLDriver         *self,
+                                                          const char          *directory);
+#endif
+
+static inline GskGLTexture *
+gsk_gl_driver_get_texture_by_id (GskGLDriver *self,
+                                 guint        texture_id)
+{
+  return g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id));
+}
+
+/**
+ * gsk_gl_driver_lookup_texture:
+ * @self: a `GskGLDriver`
+ * @key: the key for the texture
+ *
+ * Looks up a texture in the texture cache by @key.
+ *
+ * If the texture could not be found, then zero is returned.
+ *
+ * Returns: a positive integer if the texture was found; otherwise 0.
+ */
+static inline guint
+gsk_gl_driver_lookup_texture (GskGLDriver         *self,
+                              const GskTextureKey *key)
+{
+  gpointer id;
+
+  if (g_hash_table_lookup_extended (self->key_to_texture_id, key, NULL, &id))
+    {
+      GskGLTexture *texture = g_hash_table_lookup (self->textures, id);
+
+      if (texture != NULL)
+        texture->last_used_in_frame = self->current_frame_id;
+
+      return GPOINTER_TO_UINT (id);
+    }
+
+  return 0;
+}
+
+static inline void
+gsk_gl_driver_slice_texture (GskGLDriver        *self,
+                             GdkTexture         *texture,
+                             GskGLTextureSlice **out_slices,
+                             guint              *out_n_slices)
+{
+  GskGLTexture *t;
+
+  if ((t = gdk_texture_get_render_data (texture, self)))
+    {
+      *out_slices = t->slices;
+      *out_n_slices = t->n_slices;
+      return;
+    }
+
+  gsk_gl_driver_add_texture_slices (self, texture, out_slices, out_n_slices);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglglyphlibrary.c b/gsk/gl/gskglglyphlibrary.c
new file mode 100644 (file)
index 0000000..fd4518d
--- /dev/null
@@ -0,0 +1,326 @@
+/* gskglglyphlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemoryformatprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglglyphlibraryprivate.h"
+
+#define MAX_GLYPH_SIZE 128
+
+G_DEFINE_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskGLGlyphLibrary *
+gsk_gl_glyph_library_new (GskGLDriver *driver)
+{
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+
+  return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
+                       "driver", driver,
+                       NULL);
+}
+
+static guint
+gsk_gl_glyph_key_hash (gconstpointer data)
+{
+  const GskGLGlyphKey *key = data;
+
+  /* We do not store the hash within the key because GHashTable will already
+   * store the hash value for us and so this is called only a single time per
+   * cached item. This saves an extra 4 bytes per GskGLGlyphKey which means on
+   * 64-bit, we fit nicely within 2 pointers (the smallest allocation size
+   * for GSlice).
+   */
+
+  return GPOINTER_TO_UINT (key->font) ^
+         key->glyph ^
+         (key->xshift << 24) ^
+         (key->yshift << 26) ^
+         key->scale;
+}
+
+static gboolean
+gsk_gl_glyph_key_equal (gconstpointer v1,
+                        gconstpointer v2)
+{
+  return memcmp (v1, v2, sizeof (GskGLGlyphKey)) == 0;
+}
+
+static void
+gsk_gl_glyph_key_free (gpointer data)
+{
+  GskGLGlyphKey *key = data;
+
+  g_clear_object (&key->font);
+  g_slice_free (GskGLGlyphKey, key);
+}
+
+static void
+gsk_gl_glyph_value_free (gpointer data)
+{
+  g_slice_free (GskGLGlyphValue, data);
+}
+
+static void
+gsk_gl_glyph_library_begin_frame (GskGLTextureLibrary *library,
+                                  gint64               frame_id,
+                                  GPtrArray           *removed_atlases)
+{
+  GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)library;
+
+  memset (self->front, 0, sizeof self->front);
+}
+
+static void
+gsk_gl_glyph_library_finalize (GObject *object)
+{
+  GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)object;
+
+  g_clear_pointer (&self->surface_data, g_free);
+
+  G_OBJECT_CLASS (gsk_gl_glyph_library_parent_class)->finalize (object);
+}
+
+static void
+gsk_gl_glyph_library_class_init (GskGLGlyphLibraryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass);
+
+  object_class->finalize = gsk_gl_glyph_library_finalize;
+
+  library_class->begin_frame = gsk_gl_glyph_library_begin_frame;
+}
+
+static void
+gsk_gl_glyph_library_init (GskGLGlyphLibrary *self)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+
+  tl->max_entry_size = MAX_GLYPH_SIZE;
+  gsk_gl_texture_library_set_funcs (tl,
+                                    gsk_gl_glyph_key_hash,
+                                    gsk_gl_glyph_key_equal,
+                                    gsk_gl_glyph_key_free,
+                                    gsk_gl_glyph_value_free);
+}
+
+static cairo_surface_t *
+gsk_gl_glyph_library_create_surface (GskGLGlyphLibrary *self,
+                                     int                stride,
+                                     int                width,
+                                     int                height,
+                                     int                uwidth,
+                                     int                uheight)
+{
+  cairo_surface_t *surface;
+  gsize n_bytes;
+
+  g_assert (GSK_IS_GL_GLYPH_LIBRARY (self));
+  g_assert (width > 0);
+  g_assert (height > 0);
+
+  n_bytes = stride * height;
+
+  if G_LIKELY (n_bytes > self->surface_data_len)
+    {
+      self->surface_data = g_realloc (self->surface_data, n_bytes);
+      self->surface_data_len = n_bytes;
+    }
+
+  memset (self->surface_data, 0, n_bytes);
+  surface = cairo_image_surface_create_for_data (self->surface_data,
+                                                 CAIRO_FORMAT_ARGB32,
+                                                 width, height, stride);
+  cairo_surface_set_device_scale (surface, width / (double)uwidth, height / (double)uheight);
+
+  return surface;
+}
+
+static void
+render_glyph (cairo_surface_t           *surface,
+              const cairo_scaled_font_t *scaled_font,
+              const GskGLGlyphKey       *key,
+              const GskGLGlyphValue     *value)
+{
+  cairo_t *cr;
+  cairo_glyph_t glyph;
+
+  g_assert (surface != NULL);
+  g_assert (scaled_font != NULL);
+
+  cr = cairo_create (surface);
+  cairo_set_scaled_font (cr, scaled_font);
+  cairo_set_source_rgba (cr, 1, 1, 1, 1);
+
+  glyph.index = key->glyph;
+  glyph.x = 0.25 * key->xshift - value->ink_rect.x;
+  glyph.y = 0.25 * key->yshift - value->ink_rect.y;
+
+  cairo_show_glyphs (cr, &glyph, 1);
+  cairo_destroy (cr);
+
+  cairo_surface_flush (surface);
+}
+
+static void
+gsk_gl_glyph_library_upload_glyph (GskGLGlyphLibrary     *self,
+                                   const GskGLGlyphKey   *key,
+                                   const GskGLGlyphValue *value,
+                                   int                    x,
+                                   int                    y,
+                                   int                    width,
+                                   int                    height,
+                                   int                    uwidth,
+                                   int                    uheight)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+  cairo_scaled_font_t *scaled_font;
+  cairo_surface_t *surface;
+  guchar *pixel_data;
+  guchar *free_data = NULL;
+  guint gl_format;
+  guint gl_type;
+  guint texture_id;
+  gsize stride;
+
+  g_assert (GSK_IS_GL_GLYPH_LIBRARY (self));
+  g_assert (key != NULL);
+  g_assert (value != NULL);
+
+  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
+  if G_UNLIKELY (scaled_font == NULL ||
+                 cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS)
+    return;
+
+  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+
+  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                          "Uploading glyph %d",
+                                          key->glyph);
+
+  surface = gsk_gl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight);
+  render_glyph (surface, scaled_font, key, value);
+
+  texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value);
+
+  g_assert (texture_id > 0);
+
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4);
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  if G_UNLIKELY (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+    {
+      pixel_data = free_data = g_malloc (width * height * 4);
+      gdk_memory_convert (pixel_data,
+                          width * 4,
+                          GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
+                          cairo_image_surface_get_data (surface),
+                          width * 4,
+                          GDK_MEMORY_DEFAULT,
+                          width, height);
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_BYTE;
+    }
+  else
+    {
+      pixel_data = cairo_image_surface_get_data (surface);
+      gl_format = GL_BGRA;
+      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    }
+
+  glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height,
+                   gl_format, gl_type, pixel_data);
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+
+  cairo_surface_destroy (surface);
+  g_free (free_data);
+
+  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+
+  tl->driver->command_queue->n_uploads++;
+
+  if (gdk_profiler_is_running ())
+    {
+      char message[64];
+      g_snprintf (message, sizeof message, "Size %dx%d", width, height);
+      gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Glyph", message);
+    }
+}
+
+gboolean
+gsk_gl_glyph_library_add (GskGLGlyphLibrary      *self,
+                          GskGLGlyphKey          *key,
+                          const GskGLGlyphValue **out_value)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+  PangoRectangle ink_rect;
+  GskGLGlyphValue *value;
+  int width;
+  int height;
+  guint packed_x;
+  guint packed_y;
+
+  g_assert (GSK_IS_GL_GLYPH_LIBRARY (self));
+  g_assert (key != NULL);
+  g_assert (out_value != NULL);
+
+  pango_font_get_glyph_extents (key->font, key->glyph, &ink_rect, NULL);
+  pango_extents_to_pixels (&ink_rect, NULL);
+
+  ink_rect.x -= 1;
+  ink_rect.width += 2;
+  ink_rect.y -= 1;
+  ink_rect.height += 2;
+
+  width = (int) ceil (ink_rect.width * key->scale / 1024.0);
+  height = (int) ceil (ink_rect.height * key->scale / 1024.0);
+
+  value = gsk_gl_texture_library_pack (tl,
+                                       key,
+                                       sizeof *value,
+                                       width,
+                                       height,
+                                       1,
+                                       &packed_x, &packed_y);
+
+  memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect);
+
+  if (key->scale > 0 && width > 0 && height > 0)
+    gsk_gl_glyph_library_upload_glyph (self,
+                                       key,
+                                       value,
+                                       packed_x + 1,
+                                       packed_y + 1,
+                                       width,
+                                       height,
+                                       ink_rect.width,
+                                       ink_rect.height);
+
+  *out_value = value;
+
+  return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
+}
diff --git a/gsk/gl/gskglglyphlibraryprivate.h b/gsk/gl/gskglglyphlibraryprivate.h
new file mode 100644 (file)
index 0000000..2ed0513
--- /dev/null
@@ -0,0 +1,103 @@
+/* gskglglyphlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_GLYPH_LIBRARY_PRIVATE_H__
+#define __GSK_GL_GLYPH_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_gl_glyph_library_get_type())
+
+typedef struct _GskGLGlyphKey
+{
+  PangoFont *font;
+  PangoGlyph glyph;
+  guint xshift : 2;
+  guint yshift : 2;
+  guint scale  : 28; /* times 1024 */
+} GskGLGlyphKey;
+
+typedef struct _GskGLGlyphValue
+{
+  GskGLTextureAtlasEntry entry;
+  PangoRectangle ink_rect;
+} GskGLGlyphValue;
+
+#if GLIB_SIZEOF_VOID_P == 8
+G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 16);
+#elif GLIB_SIZEOF_VOID_P == 4
+G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 12);
+#endif
+
+G_DECLARE_FINAL_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK, GL_GLYPH_LIBRARY, GskGLTextureLibrary)
+
+struct _GskGLGlyphLibrary
+{
+  GskGLTextureLibrary parent_instance;
+  guint8 *surface_data;
+  gsize surface_data_len;
+  struct {
+    GskGLGlyphKey key;
+    const GskGLGlyphValue *value;
+  } front[256];
+};
+
+GskGLGlyphLibrary *gsk_gl_glyph_library_new (GskGLDriver            *driver);
+gboolean           gsk_gl_glyph_library_add (GskGLGlyphLibrary      *self,
+                                             GskGLGlyphKey          *key,
+                                             const GskGLGlyphValue **out_value);
+
+static inline guint
+gsk_gl_glyph_library_lookup_or_add (GskGLGlyphLibrary      *self,
+                                    const GskGLGlyphKey    *key,
+                                    const GskGLGlyphValue **out_value)
+{
+  GskGLTextureAtlasEntry *entry;
+  guint front_index = ((key->glyph << 2) | key->xshift) & 0xFF;
+
+  if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
+    {
+      *out_value = self->front[front_index].value;
+    }
+  else if (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry))
+    {
+      *out_value = (GskGLGlyphValue *)entry;
+      self->front[front_index].key = *key;
+      self->front[front_index].value = *out_value;
+    }
+  else
+    {
+      GskGLGlyphKey *k = g_slice_copy (sizeof *key, key);
+      g_object_ref (k->font);
+      gsk_gl_glyph_library_add (self, k, out_value);
+      self->front[front_index].key = *key;
+      self->front[front_index].value = *out_value;
+    }
+
+  return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_GLYPH_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/gl/gskgliconlibrary.c b/gsk/gl/gskgliconlibrary.c
new file mode 100644 (file)
index 0000000..f4686fd
--- /dev/null
@@ -0,0 +1,216 @@
+/* gskgliconlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemoryformatprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdktextureprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskgliconlibraryprivate.h"
+
+struct _GskGLIconLibrary
+{
+  GskGLTextureLibrary parent_instance;
+};
+
+G_DEFINE_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskGLIconLibrary *
+gsk_gl_icon_library_new (GskGLDriver *driver)
+{
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+
+  return g_object_new (GSK_TYPE_GL_ICON_LIBRARY,
+                       "driver", driver,
+                       NULL);
+}
+
+static void
+gsk_gl_icon_data_free (gpointer data)
+{
+  GskGLIconData *icon_data = data;
+
+  g_clear_object (&icon_data->source_texture);
+  g_slice_free (GskGLIconData, icon_data);
+}
+
+static void
+gsk_gl_icon_library_class_init (GskGLIconLibraryClass *klass)
+{
+}
+
+static void
+gsk_gl_icon_library_init (GskGLIconLibrary *self)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+
+  tl->max_entry_size = 128;
+  gsk_gl_texture_library_set_funcs (tl,
+                                    NULL, NULL, NULL,
+                                    gsk_gl_icon_data_free);
+}
+
+void
+gsk_gl_icon_library_add (GskGLIconLibrary     *self,
+                         GdkTexture           *key,
+                         const GskGLIconData **out_value)
+{
+  GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self;
+  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+  cairo_surface_t *surface;
+  GskGLIconData *icon_data;
+  guint8 *pixel_data;
+  guint8 *surface_data;
+  guint8 *free_data = NULL;
+  guint gl_format;
+  guint gl_type;
+  guint packed_x;
+  guint packed_y;
+  int width;
+  int height;
+  guint texture_id;
+
+  g_assert (GSK_IS_GL_ICON_LIBRARY (self));
+  g_assert (GDK_IS_TEXTURE (key));
+  g_assert (out_value != NULL);
+
+  width = key->width;
+  height = key->height;
+
+  icon_data = gsk_gl_texture_library_pack (tl,
+                                           key,
+                                           sizeof (GskGLIconData),
+                                           width, height, 1,
+                                           &packed_x, &packed_y);
+  icon_data->source_texture = g_object_ref (key);
+
+  /* actually upload the texture */
+  surface = gdk_texture_download_surface (key);
+  surface_data = cairo_image_surface_get_data (surface);
+  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                          "Uploading texture");
+
+  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+    {
+      pixel_data = free_data = g_malloc (width * height * 4);
+      gdk_memory_convert (pixel_data, width * 4,
+                          GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
+                          surface_data, cairo_image_surface_get_stride (surface),
+                          GDK_MEMORY_DEFAULT, width, height);
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_BYTE;
+    }
+  else
+    {
+      pixel_data = surface_data;
+      gl_format = GL_BGRA;
+      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    }
+
+  texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
+
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + 1, packed_y + 1,
+                   width, height,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding top */
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + 1, packed_y,
+                   width, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding left */
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x, packed_y + 1,
+                   1, height,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding top left */
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x, packed_y,
+                   1, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+
+  /* Padding right */
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
+  glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + width + 1, packed_y + 1,
+                   1, height,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding top right */
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + width + 1, packed_y,
+                   1, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding bottom */
+  glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+  glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1);
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + 1, packed_y + 1 + height,
+                   width, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding bottom left */
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x, packed_y + 1 + height,
+                   1, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Padding bottom right */
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
+  glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   packed_x + 1 + width, packed_y + 1 + height,
+                   1, 1,
+                   gl_format, gl_type,
+                   pixel_data);
+  /* Reset this */
+  glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+  glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
+
+  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+
+  *out_value = icon_data;
+
+  cairo_surface_destroy (surface);
+  g_free (free_data);
+
+  tl->driver->command_queue->n_uploads++;
+
+  if (gdk_profiler_is_running ())
+    {
+      char message[64];
+      g_snprintf (message, sizeof message, "Size %dx%d", width, height);
+      gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Icon", message);
+    }
+}
diff --git a/gsk/gl/gskgliconlibraryprivate.h b/gsk/gl/gskgliconlibraryprivate.h
new file mode 100644 (file)
index 0000000..8c34aec
--- /dev/null
@@ -0,0 +1,60 @@
+/* gskgliconlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_ICON_LIBRARY_PRIVATE_H__
+#define __GSK_GL_ICON_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_ICON_LIBRARY (gsk_gl_icon_library_get_type())
+
+typedef struct _GskGLIconData
+{
+  GskGLTextureAtlasEntry entry;
+  GdkTexture *source_texture;
+} GskGLIconData;
+
+G_DECLARE_FINAL_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK, GL_ICON_LIBRARY, GskGLTextureLibrary)
+
+GskGLIconLibrary *gsk_gl_icon_library_new (GskGLDriver          *driver);
+void              gsk_gl_icon_library_add (GskGLIconLibrary     *self,
+                                           GdkTexture           *key,
+                                           const GskGLIconData **out_value);
+
+static inline void
+gsk_gl_icon_library_lookup_or_add (GskGLIconLibrary     *self,
+                                   GdkTexture           *key,
+                                   const GskGLIconData **out_value)
+{
+  GskGLTextureAtlasEntry *entry;
+
+  if G_LIKELY (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry))
+    *out_value = (GskGLIconData *)entry;
+  else
+    gsk_gl_icon_library_add (self, key, out_value);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_ICON_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/gl/gskglprofiler.c b/gsk/gl/gskglprofiler.c
new file mode 100644 (file)
index 0000000..9b834e5
--- /dev/null
@@ -0,0 +1,181 @@
+#include "config.h"
+
+#include "gskglprofilerprivate.h"
+
+#include <epoxy/gl.h>
+
+#define N_QUERIES       4
+
+struct _GskGLProfiler
+{
+  GObject parent_instance;
+
+  GdkGLContext *gl_context;
+
+  /* Creating GL queries is kind of expensive, so we pay the
+   * price upfront and create a circular buffer of queries
+   */
+  GLuint gl_queries[N_QUERIES];
+  GLuint active_query;
+
+  gboolean has_queries : 1;
+  gboolean has_timer : 1;
+  gboolean first_frame : 1;
+};
+
+enum {
+  PROP_GL_CONTEXT = 1,
+
+  N_PROPERTIES
+};
+
+static GParamSpec *gsk_gl_profiler_properties[N_PROPERTIES];
+
+G_DEFINE_TYPE (GskGLProfiler, gsk_gl_profiler, G_TYPE_OBJECT)
+
+static void
+gsk_gl_profiler_finalize (GObject *gobject)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  if (self->has_queries)
+    glDeleteQueries (N_QUERIES, self->gl_queries);
+
+  g_clear_object (&self->gl_context);
+
+  G_OBJECT_CLASS (gsk_gl_profiler_parent_class)->finalize (gobject);
+}
+
+static void
+gsk_gl_profiler_set_property (GObject      *gobject,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      self->gl_context = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_profiler_get_property (GObject    *gobject,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_GL_CONTEXT:
+      g_value_set_object (value, self->gl_context);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_profiler_class_init (GskGLProfilerClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->set_property = gsk_gl_profiler_set_property;
+  gobject_class->get_property = gsk_gl_profiler_get_property;
+  gobject_class->finalize = gsk_gl_profiler_finalize;
+
+  gsk_gl_profiler_properties[PROP_GL_CONTEXT] =
+    g_param_spec_object ("gl-context",
+                         "GL Context",
+                         "The GdkGLContext used by the GL profiler",
+                         GDK_TYPE_GL_CONTEXT,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPERTIES, gsk_gl_profiler_properties);
+}
+
+static void
+gsk_gl_profiler_init (GskGLProfiler *self)
+{
+  self->has_queries = epoxy_is_desktop_gl();
+  self->has_timer = epoxy_is_desktop_gl() && (epoxy_gl_version () >= 33 || epoxy_has_gl_extension ("GL_ARB_timer_query"));
+
+  if (!self->has_queries)
+    return;
+
+  glGenQueries (N_QUERIES, self->gl_queries);
+  self->first_frame = TRUE;
+}
+
+GskGLProfiler *
+gsk_gl_profiler_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_PROFILER, "gl-context", context, NULL);
+}
+
+void
+gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler)
+{
+  GLuint query_id;
+
+  g_return_if_fail (GSK_IS_GL_PROFILER (profiler));
+
+  if (!profiler->has_timer || !profiler->has_queries)
+    return;
+
+  query_id = profiler->gl_queries[profiler->active_query];
+  glBeginQuery (GL_TIME_ELAPSED, query_id);
+}
+
+guint64
+gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler)
+{
+  GLuint last_query_id;
+  GLint res;
+  GLuint64 elapsed;
+
+  g_return_val_if_fail (GSK_IS_GL_PROFILER (profiler), 0);
+
+  if (!profiler->has_timer || !profiler->has_queries)
+    return 0;
+
+  glEndQuery (GL_TIME_ELAPSED);
+
+  if (profiler->active_query == 0)
+    last_query_id = N_QUERIES - 1;
+  else
+    last_query_id = profiler->active_query - 1;
+
+  /* Advance iterator */
+  profiler->active_query += 1;
+  if (profiler->active_query == N_QUERIES)
+    profiler->active_query = 0;
+
+  /* If this is the first frame we already have a result */
+  if (profiler->first_frame)
+    {
+      profiler->first_frame = FALSE;
+      return 0;
+    }
+
+  glGetQueryObjectiv (profiler->gl_queries[last_query_id], GL_QUERY_RESULT_AVAILABLE, &res);
+  if (res == 1)
+    glGetQueryObjectui64v (profiler->gl_queries[last_query_id], GL_QUERY_RESULT, &elapsed);
+  else
+    elapsed = 0;
+
+  return elapsed / 1000; /* Convert to usec to match other profiler APIs */
+}
diff --git a/gsk/gl/gskglprofilerprivate.h b/gsk/gl/gskglprofilerprivate.h
new file mode 100644 (file)
index 0000000..5b2a24b
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef __GSK_GL_PROFILER_PRIVATE_H__
+#define __GSK_GL_PROFILER_PRIVATE_H__
+
+#include <gsk/gsktypes.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_PROFILER (gsk_gl_profiler_get_type ())
+G_DECLARE_FINAL_TYPE (GskGLProfiler, gsk_gl_profiler, GSK, GL_PROFILER, GObject)
+
+GskGLProfiler * gsk_gl_profiler_new                     (GdkGLContext  *context);
+
+void            gsk_gl_profiler_begin_gpu_region        (GskGLProfiler *profiler);
+guint64         gsk_gl_profiler_end_gpu_region          (GskGLProfiler *profiler);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_PROFILER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglprogram.c b/gsk/gl/gskglprogram.c
new file mode 100644 (file)
index 0000000..66171b5
--- /dev/null
@@ -0,0 +1,173 @@
+/* gskglprogram.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gskglcommandqueueprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskgluniformstateprivate.h"
+
+G_DEFINE_TYPE (GskGLProgram, gsk_gl_program, G_TYPE_OBJECT)
+
+GskGLProgram *
+gsk_gl_program_new (GskGLDriver *driver,
+                    const char  *name,
+                    int          program_id)
+{
+  GskGLProgram *self;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+  g_return_val_if_fail (program_id >= -1, NULL);
+
+  self = g_object_new (GSK_TYPE_GL_PROGRAM, NULL);
+  self->id = program_id;
+  self->name = g_strdup (name);
+  self->driver = g_object_ref (driver);
+  self->n_mappings = 0;
+
+  return self;
+}
+
+static void
+gsk_gl_program_finalize (GObject *object)
+{
+  GskGLProgram *self = (GskGLProgram *)object;
+
+  if (self->id >= 0)
+    g_warning ("Leaking GLSL program %d (%s)",
+               self->id,
+               self->name ? self->name : "");
+
+  g_clear_pointer (&self->name, g_free);
+  g_clear_object (&self->driver);
+
+  G_OBJECT_CLASS (gsk_gl_program_parent_class)->finalize (object);
+}
+
+static void
+gsk_gl_program_class_init (GskGLProgramClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gsk_gl_program_finalize;
+}
+
+static void
+gsk_gl_program_init (GskGLProgram *self)
+{
+  self->id = -1;
+
+  for (guint i = 0; i < G_N_ELEMENTS (self->mappings); i++)
+    self->mappings[i].location = -1;
+}
+
+/**
+ * gsk_gl_program_add_uniform:
+ * @self: a `GskGLProgram`
+ * @name: the name of the uniform such as "u_source"
+ * @key: the identifier to use for the uniform
+ *
+ * This method will create a mapping between @key and the location
+ * of the uniform on the GPU. This simplifies calling code to not
+ * need to know where the uniform location is and only register it
+ * when creating the program.
+ *
+ * You might use this with an enum of all your uniforms for the
+ * program and then register each of them like:
+ *
+ * ```
+ * gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SOURCE);
+ * ```
+ *
+ * That allows you to set values for the program with something
+ * like the following:
+ *
+ * ```
+ * gsk_gl_program_set_uniform1i (program, UNIFORM_SOURCE, 1);
+ * ```
+ *
+ * Returns: %TRUE if the uniform was found; otherwise %FALSE
+ */
+gboolean
+gsk_gl_program_add_uniform (GskGLProgram *self,
+                            const char   *name,
+                            guint         key)
+{
+  GLint location;
+
+  g_return_val_if_fail (GSK_IS_GL_PROGRAM (self), FALSE);
+  g_return_val_if_fail (name != NULL, FALSE);
+  g_return_val_if_fail (key < G_N_ELEMENTS (self->mappings), FALSE);
+
+  location = glGetUniformLocation (self->id, name);
+
+  /* Register the information even if unused */
+  self->mappings[key].name = g_intern_string (name);
+  self->mappings[key].location = location;
+  if (key >= self->n_mappings)
+    self->n_mappings = key + 1;
+
+#if 0
+  g_print ("program [%d] %s uniform %s [%u of %u] at location %d.\n",
+           self->id, self->name, name, key, self->n_mappings, location);
+#endif
+
+  return location > -1;
+}
+
+/**
+ * gsk_gl_program_delete:
+ * @self: a `GskGLProgram`
+ *
+ * Deletes the GLSL program.
+ */
+void
+gsk_gl_program_delete (GskGLProgram *self)
+{
+  g_return_if_fail (GSK_IS_GL_PROGRAM (self));
+  g_return_if_fail (self->driver->command_queue != NULL);
+
+  gsk_gl_command_queue_delete_program (self->driver->command_queue, self->id);
+  self->id = -1;
+}
+
+/**
+ * gsk_gl_program_uniforms_added:
+ * @self: a `GskGLProgram`
+ * @has_attachments: if any uniform is for a bind/texture attachment
+ *
+ * This function should be called after all of the uniforms ahve
+ * been added with gsk_gl_program_add_uniform().
+ *
+ * This function will setup the uniform state so that the program
+ * has fast access to the data buffers without as many lookups at
+ * runtime for comparison data.
+ */
+void
+gsk_gl_program_uniforms_added (GskGLProgram *self,
+                               gboolean      has_attachments)
+{
+  g_return_if_fail (GSK_IS_GL_PROGRAM (self));
+  g_return_if_fail (self->uniforms == NULL);
+
+  self->uniforms = self->driver->command_queue->uniforms;
+  self->program_info = gsk_gl_uniform_state_get_program (self->uniforms, self->id, self->mappings, self->n_mappings);
+  self->program_info->has_attachments = has_attachments;
+}
diff --git a/gsk/gl/gskglprogramprivate.h b/gsk/gl/gskglprogramprivate.h
new file mode 100644 (file)
index 0000000..7693ed9
--- /dev/null
@@ -0,0 +1,282 @@
+/* gskglprogramprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_PROGRAM_PRIVATE_H__
+#define __GSK_GL_PROGRAM_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+
+#include "gskglattachmentstateprivate.h"
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_PROGRAM (gsk_gl_program_get_type())
+#define GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES 4
+#define GSK_GL_PROGRAM_MAX_CUSTOM_ARGS 8
+
+G_DECLARE_FINAL_TYPE (GskGLProgram, gsk_gl_program, GSK, GL_PROGRAM, GObject)
+
+struct _GskGLProgram
+{
+  GObject parent_instance;
+
+  int id;
+  char *name;
+  GskGLDriver *driver;
+
+  /* Cached pointer to avoid lots of pointer chasing/lookups */
+  GskGLUniformState *uniforms;
+  GskGLUniformProgram *program_info;
+
+  /* Static array for key->location transforms */
+  GskGLUniformMapping mappings[32];
+  guint n_mappings;
+};
+
+GskGLProgram * gsk_gl_program_new            (GskGLDriver  *driver,
+                                              const char   *name,
+                                              int           program_id);
+gboolean       gsk_gl_program_add_uniform    (GskGLProgram *self,
+                                              const char   *name,
+                                              guint         key);
+void           gsk_gl_program_uniforms_added (GskGLProgram *self,
+                                              gboolean      has_attachments);
+void           gsk_gl_program_delete         (GskGLProgram *self);
+
+static inline void
+gsk_gl_program_set_uniform1fv (GskGLProgram *self,
+                               guint         key,
+                               guint         stamp,
+                               guint         count,
+                               const float  *values)
+{
+  gsk_gl_uniform_state_set1fv (self->uniforms, self->program_info,
+                               key,
+                               stamp,
+                               count,
+                               values);
+}
+
+static inline void
+gsk_gl_program_set_uniform2fv (GskGLProgram *self,
+                               guint         key,
+                               guint         stamp,
+                               guint         count,
+                               const float  *values)
+{
+  gsk_gl_uniform_state_set2fv (self->uniforms, self->program_info,
+                               key,
+                               stamp,
+                               count,
+                               values);
+}
+
+static inline void
+gsk_gl_program_set_uniform4fv (GskGLProgram *self,
+                               guint         key,
+                               guint         stamp,
+                               guint         count,
+                               const float  *values)
+{
+  gsk_gl_uniform_state_set4fv (self->uniforms, self->program_info,
+                               key,
+                               stamp,
+                               count,
+                               values);
+}
+
+static inline void
+gsk_gl_program_set_uniform_rounded_rect (GskGLProgram         *self,
+                                         guint                 key,
+                                         guint                 stamp,
+                                         const GskRoundedRect *rounded_rect)
+{
+  gsk_gl_uniform_state_set_rounded_rect (self->uniforms, self->program_info,
+                                         key,
+                                         stamp,
+                                         rounded_rect);
+}
+
+static inline void
+gsk_gl_program_set_uniform1i (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              int           value0)
+{
+  gsk_gl_uniform_state_set1i (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0);
+}
+
+static inline void
+gsk_gl_program_set_uniform2i (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              int           value0,
+                              int           value1)
+{
+  gsk_gl_uniform_state_set2i (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1);
+}
+
+static inline void
+gsk_gl_program_set_uniform3i (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              int           value0,
+                              int           value1,
+                              int           value2)
+{
+  gsk_gl_uniform_state_set3i (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1, value2);
+}
+
+static inline void
+gsk_gl_program_set_uniform4i (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              int           value0,
+                              int           value1,
+                              int           value2,
+                              int           value3)
+{
+  gsk_gl_uniform_state_set4i (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1, value2, value3);
+}
+
+static inline void
+gsk_gl_program_set_uniform1f (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              float         value0)
+{
+  gsk_gl_uniform_state_set1f (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0);
+}
+
+static inline void
+gsk_gl_program_set_uniform2f (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              float         value0,
+                              float         value1)
+{
+  gsk_gl_uniform_state_set2f (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1);
+}
+
+static inline void
+gsk_gl_program_set_uniform3f (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              float         value0,
+                              float         value1,
+                              float         value2)
+{
+  gsk_gl_uniform_state_set3f (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1, value2);
+}
+
+static inline void
+gsk_gl_program_set_uniform4f (GskGLProgram *self,
+                              guint         key,
+                              guint         stamp,
+                              float         value0,
+                              float         value1,
+                              float         value2,
+                              float         value3)
+{
+  gsk_gl_uniform_state_set4f (self->uniforms,
+                              self->program_info,
+                              key,
+                              stamp,
+                              value0, value1, value2, value3);
+}
+
+static inline void
+gsk_gl_program_set_uniform_color (GskGLProgram  *self,
+                                  guint          key,
+                                  guint          stamp,
+                                  const GdkRGBA *color)
+{
+  gsk_gl_uniform_state_set_color (self->uniforms,
+                                  self->program_info,
+                                  key,
+                                  stamp,
+                                  color);
+}
+
+static inline void
+gsk_gl_program_set_uniform_texture (GskGLProgram *self,
+                                    guint         key,
+                                    guint         stamp,
+                                    GLenum        texture_target,
+                                    GLenum        texture_slot,
+                                    guint         texture_id)
+{
+  gsk_gl_attachment_state_bind_texture (self->driver->command_queue->attachments,
+                                        texture_target,
+                                        texture_slot,
+                                        texture_id);
+  gsk_gl_uniform_state_set_texture (self->uniforms,
+                                    self->program_info,
+                                    key,
+                                    stamp,
+                                    texture_slot);
+}
+
+static inline void
+gsk_gl_program_set_uniform_matrix (GskGLProgram            *self,
+                                   guint                    key,
+                                   guint                    stamp,
+                                   const graphene_matrix_t *matrix)
+{
+  gsk_gl_uniform_state_set_matrix (self->uniforms,
+                                   self->program_info,
+                                   key,
+                                   stamp,
+                                   matrix);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_PROGRAM_PRIVATE_H__ */
diff --git a/gsk/gl/gskglprograms.defs b/gsk/gl/gskglprograms.defs
new file mode 100644 (file)
index 0000000..a6d9515
--- /dev/null
@@ -0,0 +1,84 @@
+GSK_GL_DEFINE_PROGRAM (blend,
+                       "/org/gtk/libgsk/gl/blend.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2)
+                       GSK_GL_ADD_UNIFORM (2, BLEND_MODE, u_mode))
+
+GSK_GL_DEFINE_PROGRAM (blit,
+                       "/org/gtk/libgsk/gl/blit.glsl",
+                       GSK_GL_NO_UNIFORMS)
+
+GSK_GL_DEFINE_PROGRAM (blur,
+                       "/org/gtk/libgsk/gl/blur.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius)
+                       GSK_GL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size)
+                       GSK_GL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir))
+
+GSK_GL_DEFINE_PROGRAM (border,
+                       "/org/gtk/libgsk/gl/border.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BORDER_WIDTHS, u_widths)
+                       GSK_GL_ADD_UNIFORM (2, BORDER_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (color,
+                       "/org/gtk/libgsk/gl/color.glsl",
+                       GSK_GL_NO_UNIFORMS)
+
+GSK_GL_DEFINE_PROGRAM (coloring,
+                       "/org/gtk/libgsk/gl/coloring.glsl",
+                       GSK_GL_NO_UNIFORMS)
+
+GSK_GL_DEFINE_PROGRAM (color_matrix,
+                       "/org/gtk/libgsk/gl/color_matrix.glsl",
+                       GSK_GL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix)
+                       GSK_GL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset))
+
+GSK_GL_DEFINE_PROGRAM (conic_gradient,
+                       "/org/gtk/libgsk/gl/conic_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, CONIC_GRADIENT_GEOMETRY, u_geometry))
+
+GSK_GL_DEFINE_PROGRAM (cross_fade,
+                       "/org/gtk/libgsk/gl/cross_fade.glsl",
+                       GSK_GL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress)
+                       GSK_GL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2))
+
+GSK_GL_DEFINE_PROGRAM (filled_border,
+                       "/org/gtk/libgsk/gl/filled_border.glsl",
+                       GSK_GL_ADD_UNIFORM (1, FILLED_BORDER_WIDTHS, u_widths)
+                       GSK_GL_ADD_UNIFORM (2, FILLED_BORDER_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (inset_shadow,
+                       "/org/gtk/libgsk/gl/inset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, INSET_SHADOW_SPREAD, u_spread)
+                       GSK_GL_ADD_UNIFORM (2, INSET_SHADOW_OFFSET, u_offset)
+                       GSK_GL_ADD_UNIFORM (3, INSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (linear_gradient,
+                       "/org/gtk/libgsk/gl/linear_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points)
+                       GSK_GL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat))
+
+GSK_GL_DEFINE_PROGRAM (outset_shadow,
+                       "/org/gtk/libgsk/gl/outset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (radial_gradient,
+                       "/org/gtk/libgsk/gl/radial_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, RADIAL_GRADIENT_REPEAT, u_repeat)
+                       GSK_GL_ADD_UNIFORM (4, RADIAL_GRADIENT_RANGE, u_range)
+                       GSK_GL_ADD_UNIFORM (5, RADIAL_GRADIENT_GEOMETRY, u_geometry))
+
+GSK_GL_DEFINE_PROGRAM (repeat,
+                       "/org/gtk/libgsk/gl/repeat.glsl",
+                       GSK_GL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds)
+                       GSK_GL_ADD_UNIFORM (2, REPEAT_TEXTURE_RECT, u_texture_rect))
+
+GSK_GL_DEFINE_PROGRAM (unblurred_outset_shadow,
+                       "/org/gtk/libgsk/gl/unblurred_outset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
+                       GSK_GL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
+                       GSK_GL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
diff --git a/gsk/gl/gskglrenderer.c b/gsk/gl/gskglrenderer.c
new file mode 100644 (file)
index 0000000..e561370
--- /dev/null
@@ -0,0 +1,322 @@
+/* gskglrenderer.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdkdisplayprivate.h>
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdksurfaceprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskrendererprivate.h>
+#include <gsk/gskrendernodeprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskglrenderjobprivate.h"
+#include "gskglrendererprivate.h"
+
+struct _GskGLRendererClass
+{
+  GskRendererClass parent_class;
+};
+
+struct _GskGLRenderer
+{
+  GskRenderer parent_instance;
+
+  /* This context is used to swap buffers when we are rendering directly
+   * to a GDK surface. It is also used to locate the shared driver for
+   * the display that we use to drive the command queue.
+   */
+  GdkGLContext *context;
+
+  /* Our command queue is private to this renderer and talks to the GL
+   * context for our target surface. This ensure that framebuffer 0 matches
+   * the surface we care about. Since the context is shared with other
+   * contexts from other renderers on the display, texture atlases,
+   * programs, and other objects are available to them all.
+   */
+  GskGLCommandQueue *command_queue;
+
+  /* The driver manages our program state and command queues. It also
+   * deals with caching textures, shaders, shadows, glyph, and icon
+   * caches through various helpers.
+   */
+  GskGLDriver *driver;
+};
+
+G_DEFINE_TYPE (GskGLRenderer, gsk_gl_renderer, GSK_TYPE_RENDERER)
+
+/**
+ * gsk_gl_renderer_new:
+ *
+ * Creates a new `GskRenderer` using the new OpenGL renderer.
+ *
+ * Returns: a new GL renderer
+ *
+ * Since: 4.2
+ */
+GskRenderer *
+gsk_gl_renderer_new (void)
+{
+  return g_object_new (GSK_TYPE_GL_RENDERER, NULL);
+}
+
+static gboolean
+gsk_gl_renderer_realize (GskRenderer  *renderer,
+                         GdkSurface   *surface,
+                         GError      **error)
+{
+  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+  GskGLRenderer *self = (GskGLRenderer *)renderer;
+  GdkGLContext *context = NULL;
+  GskGLDriver *driver = NULL;
+  gboolean ret = FALSE;
+  gboolean debug_shaders = FALSE;
+
+  g_assert (GSK_IS_GL_RENDERER (self));
+  g_assert (GDK_IS_SURFACE (surface));
+
+  if (self->context != NULL)
+    return TRUE;
+
+  g_assert (self->driver == NULL);
+  g_assert (self->context == NULL);
+  g_assert (self->command_queue == NULL);
+
+  if (!(context = gdk_surface_create_gl_context (surface, error)) ||
+      !gdk_gl_context_realize (context, error))
+    goto failure;
+
+#ifdef G_ENABLE_DEBUG
+  if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
+    debug_shaders = TRUE;
+#endif
+
+  if (!(driver = gsk_gl_driver_for_display (gdk_surface_get_display (surface), debug_shaders, error)))
+    goto failure;
+
+  self->command_queue = gsk_gl_driver_create_command_queue (driver, context);
+  self->context = g_steal_pointer (&context);
+  self->driver = g_steal_pointer (&driver);
+
+  gsk_gl_command_queue_set_profiler (self->command_queue,
+                                     gsk_renderer_get_profiler (renderer));
+
+  ret = TRUE;
+
+failure:
+  g_clear_object (&driver);
+  g_clear_object (&context);
+
+  gdk_profiler_end_mark (start_time, "realize GskGLRenderer", NULL);
+
+  return ret;
+}
+
+static void
+gsk_gl_renderer_unrealize (GskRenderer *renderer)
+{
+  GskGLRenderer *self = (GskGLRenderer *)renderer;
+
+  g_assert (GSK_IS_GL_RENDERER (renderer));
+
+  gdk_gl_context_make_current (self->context);
+
+  g_clear_object (&self->driver);
+  g_clear_object (&self->command_queue);
+  g_clear_object (&self->context);
+}
+
+static cairo_region_t *
+get_render_region (GdkSurface   *surface,
+                   GdkGLContext *context)
+{
+  const cairo_region_t *damage;
+  GdkRectangle whole_surface;
+  GdkRectangle extents;
+
+  g_assert (GDK_IS_SURFACE (surface));
+  g_assert (GDK_IS_GL_CONTEXT (context));
+
+  whole_surface.x = 0;
+  whole_surface.y = 0;
+  whole_surface.width = gdk_surface_get_width (surface);
+  whole_surface.height = gdk_surface_get_height (surface);
+
+  /* Damage does not have scale factor applied so we can compare it to
+   * @whole_surface which also doesn't have the scale factor applied.
+   */
+  damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (context));
+
+  if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
+    return NULL;
+
+  /* If the extents match the full-scene, do the same as above */
+  cairo_region_get_extents (damage, &extents);
+  if (gdk_rectangle_equal (&extents, &whole_surface))
+    return NULL;
+
+  /* Draw clipped to the bounding-box of the region. */
+  return cairo_region_create_rectangle (&extents);
+}
+
+static void
+gsk_gl_renderer_render (GskRenderer          *renderer,
+                        GskRenderNode        *root,
+                        const cairo_region_t *update_area)
+{
+  GskGLRenderer *self = (GskGLRenderer *)renderer;
+  cairo_region_t *render_region;
+  graphene_rect_t viewport;
+  GskGLRenderJob *job;
+  GdkSurface *surface;
+  float scale_factor;
+
+  g_assert (GSK_IS_GL_RENDERER (renderer));
+  g_assert (root != NULL);
+
+  surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context));
+  scale_factor = gdk_surface_get_scale_factor (surface);
+
+  viewport.origin.x = 0;
+  viewport.origin.y = 0;
+  viewport.size.width = gdk_surface_get_width (surface) * scale_factor;
+  viewport.size.height = gdk_surface_get_height (surface) * scale_factor;
+
+  gdk_gl_context_make_current (self->context);
+  gdk_draw_context_begin_frame_full (GDK_DRAW_CONTEXT (self->context),
+                                     gsk_render_node_prefers_high_depth (root),
+                                     update_area);
+
+  /* Must be called *AFTER* gdk_draw_context_begin_frame() */
+  render_region = get_render_region (surface, self->context);
+
+  gsk_gl_driver_begin_frame (self->driver, self->command_queue);
+  job = gsk_gl_render_job_new (self->driver, &viewport, scale_factor, render_region, 0);
+#ifdef G_ENABLE_DEBUG
+  if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
+    gsk_gl_render_job_set_debug_fallback (job, TRUE);
+#endif
+  gsk_gl_render_job_render (job, root);
+  gsk_gl_driver_end_frame (self->driver);
+  gsk_gl_render_job_free (job);
+
+  gdk_gl_context_make_current (self->context);
+  gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->context));
+
+  gsk_gl_driver_after_frame (self->driver);
+
+  cairo_region_destroy (render_region);
+}
+
+static GdkTexture *
+gsk_gl_renderer_render_texture (GskRenderer           *renderer,
+                                GskRenderNode         *root,
+                                const graphene_rect_t *viewport)
+{
+  GskGLRenderer *self = (GskGLRenderer *)renderer;
+  GskGLRenderTarget *render_target;
+  GskGLRenderJob *job;
+  GdkTexture *texture = NULL;
+  guint texture_id;
+  int width;
+  int height;
+  int format;
+
+  g_assert (GSK_IS_GL_RENDERER (renderer));
+  g_assert (root != NULL);
+
+  width = ceilf (viewport->size.width);
+  height = ceilf (viewport->size.height);
+
+  format = gsk_render_node_prefers_high_depth (root) ? GL_RGBA32F : GL_RGBA8;
+
+  if (gsk_gl_driver_create_render_target (self->driver,
+                                          width, height,
+                                          format,
+                                          GL_NEAREST, GL_NEAREST,
+                                          &render_target))
+    {
+      gsk_gl_driver_begin_frame (self->driver, self->command_queue);
+      job = gsk_gl_render_job_new (self->driver, viewport, 1, NULL, render_target->framebuffer_id);
+#ifdef G_ENABLE_DEBUG
+      if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
+        gsk_gl_render_job_set_debug_fallback (job, TRUE);
+#endif
+      gsk_gl_render_job_render_flipped (job, root);
+      texture_id = gsk_gl_driver_release_render_target (self->driver, render_target, FALSE);
+      texture = gsk_gl_driver_create_gdk_texture (self->driver, texture_id);
+      gsk_gl_driver_end_frame (self->driver);
+      gsk_gl_render_job_free (job);
+
+      gsk_gl_driver_after_frame (self->driver);
+    }
+
+  return g_steal_pointer (&texture);
+}
+
+static void
+gsk_gl_renderer_dispose (GObject *object)
+{
+#ifdef G_ENABLE_DEBUG
+  GskGLRenderer *self = (GskGLRenderer *)object;
+
+  g_assert (self->driver == NULL);
+#endif
+
+  G_OBJECT_CLASS (gsk_gl_renderer_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_renderer_class_init (GskGLRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+  object_class->dispose = gsk_gl_renderer_dispose;
+
+  renderer_class->realize = gsk_gl_renderer_realize;
+  renderer_class->unrealize = gsk_gl_renderer_unrealize;
+  renderer_class->render = gsk_gl_renderer_render;
+  renderer_class->render_texture = gsk_gl_renderer_render_texture;
+}
+
+static void
+gsk_gl_renderer_init (GskGLRenderer *self)
+{
+}
+
+gboolean
+gsk_gl_renderer_try_compile_gl_shader (GskGLRenderer  *renderer,
+                                       GskGLShader    *shader,
+                                       GError        **error)
+{
+  GskGLProgram *program;
+
+  g_return_val_if_fail (GSK_IS_GL_RENDERER (renderer), FALSE);
+  g_return_val_if_fail (shader != NULL, FALSE);
+
+  program = gsk_gl_driver_lookup_shader (renderer->driver, shader, error);
+
+  return program != NULL;
+}
diff --git a/gsk/gl/gskglrenderer.h b/gsk/gl/gskglrenderer.h
new file mode 100644 (file)
index 0000000..50fa134
--- /dev/null
@@ -0,0 +1,46 @@
+/* gskglrenderer.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_RENDERER_H__
+#define __GSK_GL_RENDERER_H__
+
+#include <gsk/gskrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_RENDERER (gsk_gl_renderer_get_type())
+
+#define GSK_GL_RENDERER(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_RENDERER, GskGLRenderer))
+#define GSK_IS_GL_RENDERER(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+#define GSK_IS_GL_RENDERER_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_RENDERER))
+#define GSK_GL_RENDERER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_RENDERER, GskGLRendererClass))
+
+typedef struct _GskGLRenderer      GskGLRenderer;
+typedef struct _GskGLRendererClass GskGLRendererClass;
+
+GDK_AVAILABLE_IN_4_2
+GType        gsk_gl_renderer_get_type (void) G_GNUC_CONST;
+GDK_AVAILABLE_IN_4_2
+GskRenderer *gsk_gl_renderer_new      (void);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_RENDERER__ */
diff --git a/gsk/gl/gskglrendererprivate.h b/gsk/gl/gskglrendererprivate.h
new file mode 100644 (file)
index 0000000..df23e07
--- /dev/null
@@ -0,0 +1,34 @@
+/* gskglrendererprivate.h
+ *
+ * Copyright 2021 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_RENDERER_PRIVATE_H__
+#define __GSK_GL_RENDERER_PRIVATE_H__
+
+#include "gskglrenderer.h"
+
+G_BEGIN_DECLS
+
+gboolean gsk_gl_renderer_try_compile_gl_shader (GskGLRenderer  *renderer,
+                                                GskGLShader    *shader,
+                                                GError        **error);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_RENDERER_PRIVATE_H__ */
diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c
new file mode 100644 (file)
index 0000000..b35b115
--- /dev/null
@@ -0,0 +1,4163 @@
+/* gskglrenderjob.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdkrgbaprivate.h>
+#include <gsk/gskrendernodeprivate.h>
+#include <gsk/gskglshaderprivate.h>
+#include <gdk/gdktextureprivate.h>
+#include <gsk/gsktransformprivate.h>
+#include <gsk/gskroundedrectprivate.h>
+#include <math.h>
+#include <string.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglglyphlibraryprivate.h"
+#include "gskgliconlibraryprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskglrenderjobprivate.h"
+#include "gskglshadowlibraryprivate.h"
+
+#include "ninesliceprivate.h"
+#include "fp16private.h"
+
+#define ORTHO_NEAR_PLANE   -10000
+#define ORTHO_FAR_PLANE     10000
+#define MAX_GRADIENT_STOPS  6
+#define SHADOW_EXTRA_SIZE   4
+
+/* Make sure gradient stops fits in packed array_count */
+G_STATIC_ASSERT ((MAX_GRADIENT_STOPS * 5) < (1 << GSK_GL_UNIFORM_ARRAY_BITS));
+
+#define rounded_rect_top_left(r)                                                        \
+  (GRAPHENE_RECT_INIT(r->bounds.origin.x,                                               \
+                      r->bounds.origin.y,                                               \
+                      r->corner[0].width, r->corner[0].height))
+#define rounded_rect_top_right(r) \
+  (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[1].width,   \
+                      r->bounds.origin.y, \
+                      r->corner[1].width, r->corner[1].height))
+#define rounded_rect_bottom_right(r) \
+  (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[2].width,   \
+                      r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
+                      r->corner[2].width, r->corner[2].height))
+#define rounded_rect_bottom_left(r)                                                     \
+  (GRAPHENE_RECT_INIT(r->bounds.origin.x,                                               \
+                      r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
+                      r->corner[3].width, r->corner[3].height))
+#define rounded_rect_corner0(r)   rounded_rect_top_left(r)
+#define rounded_rect_corner1(r)   rounded_rect_top_right(r)
+#define rounded_rect_corner2(r)   rounded_rect_bottom_right(r)
+#define rounded_rect_corner3(r)   rounded_rect_bottom_left(r)
+#define rounded_rect_corner(r, i) (rounded_rect_corner##i(r))
+#define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff))
+#define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha)
+
+typedef struct _GskGLRenderClip
+{
+  GskRoundedRect rect;
+  guint          is_rectilinear : 1;
+  guint          is_fully_contained : 1;
+} GskGLRenderClip;
+
+typedef struct _GskGLRenderModelview
+{
+  GskTransform *transform;
+  float scale_x;
+  float scale_y;
+  float dx;
+  float dy;
+  float offset_x_before;
+  float offset_y_before;
+  graphene_matrix_t matrix;
+} GskGLRenderModelview;
+
+struct _GskGLRenderJob
+{
+  /* The context containing the framebuffer we are drawing to. Generally this
+   * is the context of the surface but may be a shared context if rendering to
+   * an offscreen texture such as gsk_gl_renderer_render_texture().
+   */
+  GdkGLContext *context;
+
+  /* The driver to be used. This is shared among all the renderers on a given
+   * GdkDisplay and uses the shared GL context to send commands.
+   */
+  GskGLDriver *driver;
+
+  /* The command queue (which is just a faster pointer to the driver's
+   * command queue.
+   */
+  GskGLCommandQueue *command_queue;
+
+  /* The region that we are clipping. Normalized to a single rectangle region. */
+  cairo_region_t *region;
+
+  /* The framebuffer to draw to in the @context GL context. So 0 would be the
+   * default framebuffer of @context. This is important to note as many other
+   * operations could be done using objects shared from the command queues
+   * GL context.
+   */
+  guint framebuffer;
+
+  /* The viewport we are using. This state is updated as we process render
+   * nodes in the specific visitor callbacks.
+   */
+  graphene_rect_t viewport;
+
+  /* The current projection, updated as we process nodes */
+  graphene_matrix_t projection;
+
+  /* An array of GskGLRenderModelview updated as nodes are processed. The
+   * current modelview is the last element.
+   */
+  GArray *modelview;
+
+  /* An array of GskGLRenderClip updated as nodes are processed. The
+   * current clip is the last element.
+   */
+  GArray *clip;
+
+  /* Our current alpha state as we process nodes */
+  float alpha;
+
+  /* Offset (delta x,y) as we process nodes. Occasionally this is merged into
+   * a transform that is referenced from child transform nodes.
+   */
+  float offset_x;
+  float offset_y;
+
+  /* The scale we are processing, possibly updated by transforms */
+  float scale_x;
+  float scale_y;
+
+  /* Cached pointers */
+  const GskGLRenderClip *current_clip;
+  const GskGLRenderModelview *current_modelview;
+  GskGLProgram *current_program;
+
+  /* If we should be rendering red zones over fallback nodes */
+  guint debug_fallback : 1;
+
+  /* Format we want to use for intermediate textures, determined by
+   * looking at the format of the framebuffer we are rendering on.
+   */
+  int target_format;
+};
+
+typedef struct _GskGLRenderOffscreen
+{
+  /* The bounds to render */
+  const graphene_rect_t *bounds;
+
+  /* Return location for texture coordinates */
+  struct {
+    float x;
+    float y;
+    float x2;
+    float y2;
+  } area;
+
+  /* Return location for texture ID */
+  guint texture_id;
+
+  /* Whether to force creating a new texture, even if the
+   * input already is a texture
+   */
+  guint force_offscreen : 1;
+  guint reset_clip : 1;
+  guint do_not_cache : 1;
+  guint linear_filter : 1;
+
+  /* Return location for whether we created a texture */
+  guint was_offscreen : 1;
+} GskGLRenderOffscreen;
+
+static void     gsk_gl_render_job_visit_node                (GskGLRenderJob       *job,
+                                                             const GskRenderNode  *node);
+static gboolean gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob       *job,
+                                                             const GskRenderNode  *node,
+                                                             GskGLRenderOffscreen *offscreen);
+
+static inline int
+get_target_format (GskGLRenderJob      *job,
+                   const GskRenderNode *node)
+{
+  if (gsk_render_node_prefers_high_depth (node))
+    return job->target_format;
+
+  return GL_RGBA8;
+}
+
+static inline void
+init_full_texture_region (GskGLRenderOffscreen *offscreen)
+{
+  offscreen->area.x = 0;
+  offscreen->area.y = 0;
+  offscreen->area.x2 = 1;
+  offscreen->area.y2 = 1;
+}
+
+static inline gboolean G_GNUC_PURE
+node_is_invisible (const GskRenderNode *node)
+{
+  return node->bounds.size.width == 0.0f ||
+         node->bounds.size.height == 0.0f;
+}
+
+static inline gboolean G_GNUC_PURE
+rounded_rect_equal (const GskRoundedRect *r1,
+                    const GskRoundedRect *r2)
+{
+  return memcmp (r1, r2, sizeof (GskRoundedRect)) == 0;
+}
+
+static inline void
+gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self)
+{
+  self->bounds.size.width  = MAX (self->corner[0].width + self->corner[1].width,
+                                  self->corner[3].width + self->corner[2].width);
+  self->bounds.size.height = MAX (self->corner[0].height + self->corner[3].height,
+                                  self->corner[1].height + self->corner[2].height);
+}
+
+static inline gboolean G_GNUC_PURE
+node_supports_transform (const GskRenderNode *node)
+{
+  /* Some nodes can't handle non-trivial transforms without being
+   * rendered to a texture (e.g. rotated clips, etc.). Some however work
+   * just fine, mostly because they already draw their child to a
+   * texture and just render the texture manipulated in some way, think
+   * opacity or color matrix.
+   */
+
+  switch ((int)gsk_render_node_get_node_type (node))
+    {
+      case GSK_COLOR_NODE:
+      case GSK_OPACITY_NODE:
+      case GSK_COLOR_MATRIX_NODE:
+      case GSK_TEXTURE_NODE:
+      case GSK_CROSS_FADE_NODE:
+      case GSK_LINEAR_GRADIENT_NODE:
+      case GSK_DEBUG_NODE:
+      case GSK_TEXT_NODE:
+        return TRUE;
+
+      case GSK_SHADOW_NODE:
+        return node_supports_transform (gsk_shadow_node_get_child (node));
+
+      case GSK_TRANSFORM_NODE:
+        return node_supports_transform (gsk_transform_node_get_child (node));
+
+      default:
+        return FALSE;
+    }
+}
+
+static inline gboolean G_GNUC_PURE
+color_matrix_modifies_alpha (const GskRenderNode *node)
+{
+  const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node);
+  const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node);
+  graphene_vec4_t row3;
+
+  if (graphene_vec4_get_w (offset) != 0.0f)
+    return TRUE;
+
+  graphene_matrix_get_row (matrix, 3, &row3);
+
+  return !graphene_vec4_equal (graphene_vec4_w_axis (), &row3);
+}
+
+static inline gboolean G_GNUC_PURE
+rect_contains_rect (const graphene_rect_t *r1,
+                    const graphene_rect_t *r2)
+{
+  return r2->origin.x >= r1->origin.x &&
+         (r2->origin.x + r2->size.width) <= (r1->origin.x + r1->size.width) &&
+         r2->origin.y >= r1->origin.y &&
+         (r2->origin.y + r2->size.height) <= (r1->origin.y + r1->size.height);
+}
+
+static inline gboolean
+rounded_inner_rect_contains_rect (const GskRoundedRect  *rounded,
+                                  const graphene_rect_t *rect)
+{
+  const graphene_rect_t *rounded_bounds = &rounded->bounds;
+  graphene_rect_t inner;
+  float offset_x;
+  float offset_y;
+
+  /* TODO: This is pretty conservative and we could go further,
+   *       more fine-grained checks to avoid offscreen drawing.
+   */
+
+  offset_x = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].width,
+                  rounded->corner[GSK_CORNER_BOTTOM_LEFT].width);
+  offset_y = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].height,
+                  rounded->corner[GSK_CORNER_TOP_RIGHT].height);
+
+  inner.origin.x = rounded_bounds->origin.x + offset_x;
+  inner.origin.y = rounded_bounds->origin.y + offset_y;
+  inner.size.width = rounded_bounds->size.width - offset_x -
+                     MAX (rounded->corner[GSK_CORNER_TOP_RIGHT].width,
+                          rounded->corner[GSK_CORNER_BOTTOM_RIGHT].width);
+  inner.size.height = rounded_bounds->size.height - offset_y -
+                      MAX (rounded->corner[GSK_CORNER_BOTTOM_LEFT].height,
+                           rounded->corner[GSK_CORNER_BOTTOM_RIGHT].height);
+
+  return rect_contains_rect (&inner, rect);
+}
+
+static inline gboolean G_GNUC_PURE
+rect_intersects (const graphene_rect_t *r1,
+                 const graphene_rect_t *r2)
+{
+  /* Assume both rects are already normalized, as they usually are */
+  if (r1->origin.x > (r2->origin.x + r2->size.width) ||
+      (r1->origin.x + r1->size.width) < r2->origin.x)
+    return FALSE;
+  else if (r1->origin.y > (r2->origin.y + r2->size.height) ||
+      (r1->origin.y + r1->size.height) < r2->origin.y)
+    return FALSE;
+  else
+    return TRUE;
+}
+
+static inline gboolean
+rounded_rect_has_corner (const GskRoundedRect *r,
+                         guint                 i)
+{
+  return r->corner[i].width > 0 && r->corner[i].height > 0;
+}
+
+/* Current clip is NOT rounded but new one is definitely! */
+static inline gboolean
+intersect_rounded_rectilinear (const graphene_rect_t *non_rounded,
+                               const GskRoundedRect  *rounded,
+                               GskRoundedRect        *result)
+{
+  gboolean corners[4];
+
+  /* Intersects with top left corner? */
+  corners[0] = rounded_rect_has_corner (rounded, 0) &&
+               rect_intersects (non_rounded,
+                                &rounded_rect_corner (rounded, 0));
+  if (corners[0] && !rect_contains_rect (non_rounded,
+                                         &rounded_rect_corner (rounded, 0)))
+    return FALSE;
+
+  /* top right ? */
+  corners[1] = rounded_rect_has_corner (rounded, 1) &&
+               rect_intersects (non_rounded,
+                                &rounded_rect_corner (rounded, 1));
+  if (corners[1] && !rect_contains_rect (non_rounded,
+                                         &rounded_rect_corner (rounded, 1)))
+    return FALSE;
+
+  /* bottom right ? */
+  corners[2] = rounded_rect_has_corner (rounded, 2) &&
+               rect_intersects (non_rounded,
+                                &rounded_rect_corner (rounded, 2));
+  if (corners[2] && !rect_contains_rect (non_rounded,
+                                         &rounded_rect_corner (rounded, 2)))
+    return FALSE;
+
+  /* bottom left ? */
+  corners[3] = rounded_rect_has_corner (rounded, 3) &&
+               rect_intersects (non_rounded,
+                                &rounded_rect_corner (rounded, 3));
+  if (corners[3] && !rect_contains_rect (non_rounded,
+                                         &rounded_rect_corner (rounded, 3)))
+    return FALSE;
+
+  /* We do intersect with at least one of the corners, but in such a way that the
+   * intersection between the two clips can still be represented by a single rounded
+   * rect in a trivial way. do that.
+   */
+  graphene_rect_intersection (non_rounded, &rounded->bounds, &result->bounds);
+
+  for (guint i = 0; i < 4; i++)
+    {
+      if (corners[i])
+        result->corner[i] = rounded->corner[i];
+      else
+        result->corner[i].width = result->corner[i].height = 0;
+    }
+
+  return TRUE;
+}
+
+static inline void
+init_projection_matrix (graphene_matrix_t     *projection,
+                        const graphene_rect_t *viewport)
+{
+  graphene_matrix_init_ortho (projection,
+                              viewport->origin.x,
+                              viewport->origin.x + viewport->size.width,
+                              viewport->origin.y,
+                              viewport->origin.y + viewport->size.height,
+                              ORTHO_NEAR_PLANE,
+                              ORTHO_FAR_PLANE);
+  graphene_matrix_scale (projection, 1, -1, 1);
+}
+
+static inline float
+gsk_gl_render_job_set_alpha (GskGLRenderJob *job,
+                             float           alpha)
+{
+  if (job->alpha != alpha)
+    {
+      float ret = job->alpha;
+      job->alpha = alpha;
+      job->driver->stamps[UNIFORM_SHARED_ALPHA]++;
+      return ret;
+    }
+
+  return alpha;
+}
+
+static void
+extract_matrix_metadata (GskGLRenderModelview *modelview)
+{
+  gsk_transform_to_matrix (modelview->transform, &modelview->matrix);
+
+  switch (gsk_transform_get_category (modelview->transform))
+    {
+    case GSK_TRANSFORM_CATEGORY_IDENTITY:
+      modelview->scale_x = 1;
+      modelview->scale_y = 1;
+      modelview->dx = 0;
+      modelview->dy = 0;
+      break;
+
+    case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
+      modelview->scale_x = 1;
+      modelview->scale_y = 1;
+      gsk_transform_to_translate (modelview->transform,
+                                  &modelview->dx, &modelview->dy);
+      break;
+
+    case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
+      gsk_transform_to_affine (modelview->transform,
+                               &modelview->scale_x, &modelview->scale_y,
+                               &modelview->dx, &modelview->dy);
+      break;
+
+    case GSK_TRANSFORM_CATEGORY_2D:
+      {
+        float xx, xy, yx, yy, dx, dy;
+
+        gsk_transform_to_2d (modelview->transform,
+                             &xx, &xy, &yx, &yy, &dx, &dy);
+
+        modelview->scale_x = sqrtf (xx * xx + xy * xy);
+        modelview->scale_y = sqrtf (yx * yx + yy * yy);
+      }
+      break;
+
+    case GSK_TRANSFORM_CATEGORY_UNKNOWN:
+    case GSK_TRANSFORM_CATEGORY_ANY:
+    case GSK_TRANSFORM_CATEGORY_3D:
+      {
+        graphene_vec3_t col1;
+        graphene_vec3_t col2;
+
+        /* TODO: 90% sure this is incorrect. But we should never hit this code
+         * path anyway. */
+        graphene_vec3_init (&col1,
+                            graphene_matrix_get_value (&modelview->matrix, 0, 0),
+                            graphene_matrix_get_value (&modelview->matrix, 1, 0),
+                            graphene_matrix_get_value (&modelview->matrix, 2, 0));
+
+        graphene_vec3_init (&col2,
+                            graphene_matrix_get_value (&modelview->matrix, 0, 1),
+                            graphene_matrix_get_value (&modelview->matrix, 1, 1),
+                            graphene_matrix_get_value (&modelview->matrix, 2, 1));
+
+        modelview->scale_x = graphene_vec3_length (&col1);
+        modelview->scale_y = graphene_vec3_length (&col2);
+        modelview->dx = 0;
+        modelview->dy = 0;
+      }
+      break;
+
+    default:
+      break;
+    }
+}
+
+static void
+gsk_gl_render_job_set_modelview (GskGLRenderJob *job,
+                                 GskTransform   *transform)
+{
+  GskGLRenderModelview *modelview;
+
+  g_assert (job != NULL);
+  g_assert (job->modelview != NULL);
+
+  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
+
+  g_array_set_size (job->modelview, job->modelview->len + 1);
+
+  modelview = &g_array_index (job->modelview,
+                              GskGLRenderModelview,
+                              job->modelview->len - 1);
+
+  modelview->transform = transform;
+
+  modelview->offset_x_before = job->offset_x;
+  modelview->offset_y_before = job->offset_y;
+
+  extract_matrix_metadata (modelview);
+
+  job->offset_x = 0;
+  job->offset_y = 0;
+  job->scale_x = modelview->scale_x;
+  job->scale_y = modelview->scale_y;
+
+  job->current_modelview = modelview;
+}
+
+static void
+gsk_gl_render_job_push_modelview (GskGLRenderJob *job,
+                                  GskTransform   *transform)
+{
+  GskGLRenderModelview *modelview;
+
+  g_assert (job != NULL);
+  g_assert (job->modelview != NULL);
+  g_assert (transform != NULL);
+
+  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
+
+  g_array_set_size (job->modelview, job->modelview->len + 1);
+
+  modelview = &g_array_index (job->modelview,
+                              GskGLRenderModelview,
+                              job->modelview->len - 1);
+
+  if G_LIKELY (job->modelview->len > 1)
+    {
+      GskGLRenderModelview *last;
+      GskTransform *t = NULL;
+
+      last = &g_array_index (job->modelview,
+                             GskGLRenderModelview,
+                             job->modelview->len - 2);
+
+      /* Multiply given matrix with our previous modelview */
+      t = gsk_transform_translate (gsk_transform_ref (last->transform),
+                                   &(graphene_point_t) {
+                                     job->offset_x,
+                                     job->offset_y
+                                   });
+      t = gsk_transform_transform (t, transform);
+      modelview->transform = t;
+    }
+  else
+    {
+      modelview->transform = gsk_transform_ref (transform);
+    }
+
+  modelview->offset_x_before = job->offset_x;
+  modelview->offset_y_before = job->offset_y;
+
+  extract_matrix_metadata (modelview);
+
+  job->offset_x = 0;
+  job->offset_y = 0;
+  job->scale_x = modelview->scale_x;
+  job->scale_y = modelview->scale_y;
+
+  job->current_modelview = modelview;
+}
+
+static void
+gsk_gl_render_job_pop_modelview (GskGLRenderJob *job)
+{
+  const GskGLRenderModelview *head;
+
+  g_assert (job != NULL);
+  g_assert (job->modelview);
+  g_assert (job->modelview->len > 0);
+
+  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
+
+  head = job->current_modelview;
+
+  job->offset_x = head->offset_x_before;
+  job->offset_y = head->offset_y_before;
+
+  gsk_transform_unref (head->transform);
+
+  job->modelview->len--;
+
+  if (job->modelview->len >= 1)
+    {
+      head = &g_array_index (job->modelview, GskGLRenderModelview, job->modelview->len - 1);
+
+      job->scale_x = head->scale_x;
+      job->scale_y = head->scale_y;
+
+      job->current_modelview = head;
+    }
+  else
+    {
+      job->current_modelview = NULL;
+    }
+}
+
+static void
+gsk_gl_render_job_push_clip (GskGLRenderJob       *job,
+                             const GskRoundedRect *rect)
+{
+  GskGLRenderClip *clip;
+
+  g_assert (job != NULL);
+  g_assert (job->clip != NULL);
+  g_assert (rect != NULL);
+
+  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
+
+  g_array_set_size (job->clip, job->clip->len + 1);
+
+  clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
+  memcpy (&clip->rect, rect, sizeof *rect);
+  clip->is_rectilinear = gsk_rounded_rect_is_rectilinear (rect);
+  clip->is_fully_contained = FALSE;
+
+  job->current_clip = clip;
+}
+
+static void
+gsk_gl_render_job_push_contained_clip (GskGLRenderJob *job)
+{
+  GskGLRenderClip *clip;
+  GskGLRenderClip *old_clip;
+
+  g_assert (job != NULL);
+  g_assert (job->clip != NULL);
+  g_assert (job->clip->len > 0);
+
+  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
+
+  old_clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
+
+  g_array_set_size (job->clip, job->clip->len + 1);
+
+  clip = &g_array_index (job->clip, GskGLRenderClip, job->clip->len - 1);
+  memcpy (&clip->rect.bounds, &old_clip->rect.bounds, sizeof (graphene_rect_t));
+  memset (clip->rect.corner, 0, sizeof clip->rect.corner);
+  clip->is_rectilinear = TRUE;
+  clip->is_fully_contained = TRUE;
+
+  job->current_clip = clip;
+}
+
+static void
+gsk_gl_render_job_pop_clip (GskGLRenderJob *job)
+{
+  g_assert (job != NULL);
+  g_assert (job->clip != NULL);
+  g_assert (job->clip->len > 0);
+
+  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
+  job->current_clip--;
+  job->clip->len--;
+}
+
+static inline void
+gsk_gl_render_job_offset (GskGLRenderJob *job,
+                          float           offset_x,
+                          float           offset_y)
+{
+  if (offset_x || offset_y)
+    {
+      job->offset_x += offset_x;
+      job->offset_y += offset_y;
+    }
+}
+
+static inline void
+gsk_gl_render_job_set_projection (GskGLRenderJob          *job,
+                                  const graphene_matrix_t *projection)
+{
+  memcpy (&job->projection, projection, sizeof job->projection);
+  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
+}
+
+static inline void
+gsk_gl_render_job_set_projection_from_rect (GskGLRenderJob        *job,
+                                            const graphene_rect_t *rect,
+                                            graphene_matrix_t     *prev_projection)
+{
+  if (prev_projection)
+    memcpy (prev_projection, &job->projection, sizeof *prev_projection);
+  init_projection_matrix (&job->projection, rect);
+  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
+}
+
+static inline void
+gsk_gl_render_job_set_projection_for_size (GskGLRenderJob    *job,
+                                           float              width,
+                                           float              height,
+                                           graphene_matrix_t *prev_projection)
+{
+  if (prev_projection)
+    memcpy (prev_projection, &job->projection, sizeof *prev_projection);
+  graphene_matrix_init_ortho (&job->projection, 0, width, 0, height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
+  graphene_matrix_scale (&job->projection, 1, -1, 1);
+  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
+}
+
+static inline void
+gsk_gl_render_job_set_viewport (GskGLRenderJob        *job,
+                                const graphene_rect_t *viewport,
+                                graphene_rect_t       *prev_viewport)
+{
+  if (prev_viewport)
+    memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport);
+  memcpy (&job->viewport, viewport, sizeof job->viewport);
+  job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
+}
+
+static inline void
+gsk_gl_render_job_set_viewport_for_size (GskGLRenderJob  *job,
+                                         float            width,
+                                         float            height,
+                                         graphene_rect_t *prev_viewport)
+{
+  if (prev_viewport)
+    memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport);
+  job->viewport.origin.x = 0;
+  job->viewport.origin.y = 0;
+  job->viewport.size.width = width;
+  job->viewport.size.height = height;
+  job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
+}
+
+static inline void
+gsk_gl_render_job_transform_bounds (GskGLRenderJob        *job,
+                                    const graphene_rect_t *rect,
+                                    graphene_rect_t       *out_rect)
+{
+  GskTransform *transform;
+  GskTransformCategory category;
+
+  g_assert (job != NULL);
+  g_assert (job->modelview->len > 0);
+  g_assert (rect != NULL);
+  g_assert (out_rect != NULL);
+
+  transform = job->current_modelview->transform;
+  category = gsk_transform_get_category (transform);
+
+  /* Our most common transform is 2d-affine, so inline it.
+   * Both identity and 2d-translate are virtually unseen here.
+   */
+  if G_LIKELY (category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE)
+    {
+      float scale_x = job->current_modelview->scale_x;
+      float scale_y = job->current_modelview->scale_y;
+      float dx = job->current_modelview->dx;
+      float dy = job->current_modelview->dy;
+
+      /* Init directly into out rect */
+      out_rect->origin.x = ((rect->origin.x + job->offset_x) * scale_x) + dx;
+      out_rect->origin.y = ((rect->origin.y + job->offset_y) * scale_y) + dy;
+      out_rect->size.width = rect->size.width * scale_x;
+      out_rect->size.height = rect->size.height * scale_y;
+
+      /* Normalize in place */
+      if (out_rect->size.width < 0.f)
+        {
+          float size = fabsf (out_rect->size.width);
+
+          out_rect->origin.x -= size;
+          out_rect->size.width = size;
+        }
+
+      if (out_rect->size.height < 0.f)
+        {
+          float size = fabsf (out_rect->size.height);
+
+          out_rect->origin.y -= size;
+          out_rect->size.height = size;
+        }
+    }
+  else
+    {
+      graphene_rect_t r;
+
+      r.origin.x = rect->origin.x + job->offset_x;
+      r.origin.y = rect->origin.y + job->offset_y;
+      r.size.width = rect->size.width;
+      r.size.height = rect->size.height;
+
+      gsk_transform_transform_bounds (transform, &r, out_rect);
+    }
+}
+
+static inline void
+gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob       *job,
+                                          const GskRoundedRect *rect,
+                                          GskRoundedRect       *out_rect)
+{
+  out_rect->bounds.origin.x = job->offset_x + rect->bounds.origin.x;
+  out_rect->bounds.origin.y = job->offset_y + rect->bounds.origin.y;
+  out_rect->bounds.size.width = rect->bounds.size.width;
+  out_rect->bounds.size.height = rect->bounds.size.height;
+  memcpy (out_rect->corner, rect->corner, sizeof rect->corner);
+}
+
+static inline void
+rounded_rect_get_inner (const GskRoundedRect *rect,
+                        graphene_rect_t      *inner)
+{
+  float left = MAX (rect->corner[GSK_CORNER_TOP_LEFT].width, rect->corner[GSK_CORNER_BOTTOM_LEFT].width);
+  float right = MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->corner[GSK_CORNER_BOTTOM_RIGHT].width);
+  float top = MAX (rect->corner[GSK_CORNER_TOP_LEFT].height, rect->corner[GSK_CORNER_TOP_RIGHT].height);
+  float bottom = MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height, rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
+
+  inner->origin.x = rect->bounds.origin.x + left;
+  inner->size.width = rect->bounds.size.width - (left + right);
+
+  inner->origin.y = rect->bounds.origin.y + top;
+  inner->size.height = rect->bounds.size.height - (top + bottom);
+}
+
+static inline gboolean
+interval_contains (float p1, float w1,
+                   float p2, float w2)
+{
+  if (p2 < p1)
+    return FALSE;
+
+  if (p2 + w2 > p1 + w1)
+    return FALSE;
+
+  return TRUE;
+}
+
+static inline gboolean
+gsk_gl_render_job_update_clip (GskGLRenderJob        *job,
+                               const graphene_rect_t *bounds,
+                               gboolean              *pushed_clip)
+{
+  graphene_rect_t transformed_bounds;
+  gboolean no_clip = FALSE;
+  gboolean rect_clip = FALSE;
+
+  *pushed_clip = FALSE;
+
+  if (job->current_clip->is_fully_contained)
+    {
+      /* Already fully contained - no further checks needed */
+      return TRUE;
+    }
+
+  gsk_gl_render_job_transform_bounds (job, bounds, &transformed_bounds);
+
+  if (!rect_intersects (&job->current_clip->rect.bounds, &transformed_bounds))
+    {
+      /* Completely clipped away */
+      return FALSE;
+    }
+
+  if (job->current_clip->is_rectilinear)
+    {
+      if (rect_contains_rect (&job->current_clip->rect.bounds, &transformed_bounds))
+        no_clip = TRUE;
+      else
+        rect_clip = TRUE;
+    }
+  else if (gsk_rounded_rect_contains_rect (&job->current_clip->rect, &transformed_bounds))
+    {
+      no_clip = TRUE;
+    }
+  else
+    {
+      graphene_rect_t inner;
+
+      rounded_rect_get_inner (&job->current_clip->rect, &inner);
+
+      if (interval_contains (inner.origin.x, inner.size.width,
+                             transformed_bounds.origin.x, transformed_bounds.size.width) ||
+          interval_contains (inner.origin.y, inner.size.height,
+                             transformed_bounds.origin.y, transformed_bounds.size.height))
+        rect_clip = TRUE;
+    }
+
+  if (no_clip)
+    {
+      /* This node is completely contained inside the clip.
+       * Record this fact on the clip stack, so we don't do more work
+       * for child nodes.
+       */
+
+      gsk_gl_render_job_push_contained_clip (job);
+
+      *pushed_clip = TRUE;
+    }
+  else if (rect_clip && !job->current_clip->is_rectilinear)
+    {
+      graphene_rect_t rect;
+
+      /* The clip gets simpler for this node */
+
+      graphene_rect_intersection (&job->current_clip->rect.bounds, &transformed_bounds, &rect);
+      gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (rect));
+
+      *pushed_clip = TRUE;
+    }
+
+  return TRUE;
+}
+
+static inline void
+rgba_to_half (const GdkRGBA *rgba,
+              guint16        h[4])
+{
+  float_to_half4 ((const float *)rgba, h);
+}
+
+/* fill_vertex_data */
+static void
+gsk_gl_render_job_draw_coords (GskGLRenderJob *job,
+                               float           min_x,
+                               float           min_y,
+                               float           max_x,
+                               float           max_y,
+                               float           min_u,
+                               float           min_v,
+                               float           max_u,
+                               float           max_v,
+                               guint16         c[4])
+{
+  GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+  vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { min_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
+  vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
+  vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
+  vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { max_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
+  vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
+  vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
+}
+
+/* load_vertex_data_with_region */
+static inline void
+gsk_gl_render_job_draw_offscreen_with_color (GskGLRenderJob             *job,
+                                             const graphene_rect_t      *bounds,
+                                             const GskGLRenderOffscreen *offscreen,
+                                             guint16                     color[4])
+{
+  float min_x = job->offset_x + bounds->origin.x;
+  float min_y = job->offset_y + bounds->origin.y;
+  float max_x = min_x + bounds->size.width;
+  float max_y = min_y + bounds->size.height;
+  float y1 = offscreen->was_offscreen ? offscreen->area.y2 : offscreen->area.y;
+  float y2 = offscreen->was_offscreen ? offscreen->area.y : offscreen->area.y2;
+
+  gsk_gl_render_job_draw_coords (job,
+                                 min_x, min_y, max_x, max_y,
+                                 offscreen->area.x, y1, offscreen->area.x2, y2,
+                                 color);
+}
+
+static inline void
+gsk_gl_render_job_draw_offscreen (GskGLRenderJob             *job,
+                                  const graphene_rect_t      *bounds,
+                                  const GskGLRenderOffscreen *offscreen)
+{
+  gsk_gl_render_job_draw_offscreen_with_color (job, bounds, offscreen,
+                                               (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
+}
+
+/* load_float_vertex_data */
+static inline void
+gsk_gl_render_job_draw_with_color (GskGLRenderJob *job,
+                                   float           x,
+                                   float           y,
+                                   float           width,
+                                   float           height,
+                                   guint16         color[4])
+{
+  float min_x = job->offset_x + x;
+  float min_y = job->offset_y + y;
+  float max_x = min_x + width;
+  float max_y = min_y + height;
+
+  gsk_gl_render_job_draw_coords (job, min_x, min_y, max_x, max_y, 0, 0, 1, 1, color);
+}
+
+static inline void
+gsk_gl_render_job_draw (GskGLRenderJob *job,
+                        float           x,
+                        float           y,
+                        float           width,
+                        float           height)
+{
+  gsk_gl_render_job_draw_with_color (job, x, y, width, height,
+                                     (guint16[]) { FP_ZERO, FP_ZERO, FP_ZERO, FP_ZERO });
+}
+
+/* load_vertex_data */
+static inline void
+gsk_gl_render_job_draw_rect_with_color (GskGLRenderJob        *job,
+                                        const graphene_rect_t *bounds,
+                                        guint16                color[4])
+{
+  gsk_gl_render_job_draw_with_color (job,
+                                     bounds->origin.x,
+                                     bounds->origin.y,
+                                     bounds->size.width,
+                                     bounds->size.height,
+                                     color);
+}
+static inline void
+gsk_gl_render_job_draw_rect (GskGLRenderJob        *job,
+                             const graphene_rect_t *bounds)
+{
+  gsk_gl_render_job_draw (job,
+                          bounds->origin.x,
+                          bounds->origin.y,
+                          bounds->size.width,
+                          bounds->size.height);
+}
+
+/* load_offscreen_vertex_data */
+static inline void
+gsk_gl_render_job_draw_offscreen_rect (GskGLRenderJob        *job,
+                                       const graphene_rect_t *bounds)
+{
+  float min_x = job->offset_x + bounds->origin.x;
+  float min_y = job->offset_y + bounds->origin.y;
+  float max_x = min_x + bounds->size.width;
+  float max_y = min_y + bounds->size.height;
+  guint16 color[4] = { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO };
+
+  gsk_gl_render_job_draw_coords (job,
+                                 min_x, min_y, max_x, max_y,
+                                 0, 1, 1, 0,
+                                 color);
+}
+
+static inline void
+gsk_gl_render_job_begin_draw (GskGLRenderJob *job,
+                              GskGLProgram   *program)
+{
+  job->current_program = program;
+
+  gsk_gl_command_queue_begin_draw (job->command_queue,
+                                   program->program_info,
+                                   job->viewport.size.width,
+                                   job->viewport.size.height);
+
+  gsk_gl_uniform_state_set4fv (program->uniforms,
+                               program->program_info,
+                               UNIFORM_SHARED_VIEWPORT,
+                               job->driver->stamps[UNIFORM_SHARED_VIEWPORT],
+                               1,
+                               (const float *)&job->viewport);
+
+  gsk_gl_uniform_state_set_matrix (program->uniforms,
+                                   program->program_info,
+                                   UNIFORM_SHARED_MODELVIEW,
+                                   job->driver->stamps[UNIFORM_SHARED_MODELVIEW],
+                                   &job->current_modelview->matrix);
+
+  gsk_gl_uniform_state_set_matrix (program->uniforms,
+                                   program->program_info,
+                                   UNIFORM_SHARED_PROJECTION,
+                                   job->driver->stamps[UNIFORM_SHARED_PROJECTION],
+                                   &job->projection);
+
+  gsk_gl_uniform_state_set_rounded_rect (program->uniforms,
+                                         program->program_info,
+                                         UNIFORM_SHARED_CLIP_RECT,
+                                         job->driver->stamps[UNIFORM_SHARED_CLIP_RECT],
+                                         &job->current_clip->rect);
+
+  gsk_gl_uniform_state_set1f (program->uniforms,
+                              program->program_info,
+                              UNIFORM_SHARED_ALPHA,
+                              job->driver->stamps[UNIFORM_SHARED_ALPHA],
+                              job->alpha);
+}
+
+#define CHOOSE_PROGRAM(job,name) \
+  (job->current_clip->is_fully_contained \
+      ? job->driver->name ## _no_clip \
+      : (job->current_clip->is_rectilinear \
+        ? job->driver->name ## _rect_clip \
+        : job->driver->name))
+
+static inline void
+gsk_gl_render_job_split_draw (GskGLRenderJob *job)
+{
+  gsk_gl_command_queue_split_draw (job->command_queue);
+}
+
+static inline void
+gsk_gl_render_job_end_draw (GskGLRenderJob *job)
+{
+  gsk_gl_command_queue_end_draw (job->command_queue);
+
+  job->current_program = NULL;
+}
+
+static inline void
+gsk_gl_render_job_visit_as_fallback (GskGLRenderJob      *job,
+                                     const GskRenderNode *node)
+{
+  float scale_x = job->scale_x;
+  float scale_y = job->scale_y;
+  int surface_width = ceilf (node->bounds.size.width * scale_x);
+  int surface_height = ceilf (node->bounds.size.height * scale_y);
+  GdkTexture *texture;
+  cairo_surface_t *surface;
+  cairo_surface_t *rendered_surface;
+  cairo_t *cr;
+  int cached_id;
+  int texture_id;
+  GskTextureKey key;
+
+  if (surface_width <= 0 || surface_height <= 0)
+    return;
+
+  key.pointer = node;
+  key.pointer_is_child = FALSE;
+  key.scale_x = scale_x;
+  key.scale_y = scale_y;
+  key.filter = GL_NEAREST;
+
+  cached_id = gsk_gl_driver_lookup_texture (job->driver, &key);
+
+  if (cached_id != 0)
+    {
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D, GL_TEXTURE0, cached_id);
+      gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds);
+      gsk_gl_render_job_end_draw (job);
+      return;
+    }
+
+  /* We first draw the recording surface on an image surface,
+   * just because the scaleY(-1) later otherwise screws up the
+   * rendering... */
+  {
+    rendered_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                                   surface_width,
+                                                   surface_height);
+
+    cairo_surface_set_device_scale (rendered_surface, scale_x, scale_y);
+    cr = cairo_create (rendered_surface);
+
+    cairo_save (cr);
+    cairo_translate (cr, - floorf (node->bounds.origin.x), - floorf (node->bounds.origin.y));
+    /* Render nodes don't modify state, so casting away the const is fine here */
+    gsk_render_node_draw ((GskRenderNode *)node, cr);
+    cairo_restore (cr);
+    cairo_destroy (cr);
+  }
+
+  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                        surface_width,
+                                        surface_height);
+  cairo_surface_set_device_scale (surface, scale_x, scale_y);
+  cr = cairo_create (surface);
+
+  /* We draw upside down here, so it matches what GL does. */
+  cairo_save (cr);
+  cairo_scale (cr, 1, -1);
+  cairo_translate (cr, 0, - surface_height / scale_y);
+  cairo_set_source_surface (cr, rendered_surface, 0, 0);
+  cairo_rectangle (cr, 0, 0, surface_width / scale_x, surface_height / scale_y);
+  cairo_fill (cr);
+  cairo_restore (cr);
+
+#ifdef G_ENABLE_DEBUG
+  if (job->debug_fallback)
+    {
+      cairo_move_to (cr, 0, 0);
+      cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height);
+      if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
+        cairo_set_source_rgba (cr, 0.3, 0, 1, 0.25);
+      else
+        cairo_set_source_rgba (cr, 1, 0, 0, 0.25);
+      cairo_fill_preserve (cr);
+      if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
+        cairo_set_source_rgba (cr, 0.3, 0, 1, 1);
+      else
+        cairo_set_source_rgba (cr, 1, 0, 0, 1);
+      cairo_stroke (cr);
+    }
+#endif
+  cairo_destroy (cr);
+
+  /* Create texture to upload */
+  texture = gdk_texture_new_for_surface (surface);
+  texture_id = gsk_gl_driver_load_texture (job->driver, texture,
+                                           GL_NEAREST, GL_NEAREST);
+
+  if (gdk_gl_context_has_debug (job->command_queue->context))
+    gdk_gl_context_label_object_printf (job->command_queue->context, GL_TEXTURE, texture_id,
+                                        "Fallback %s %d",
+                                        g_type_name_from_instance ((GTypeInstance *) node),
+                                        texture_id);
+
+  g_object_unref (texture);
+  cairo_surface_destroy (surface);
+  cairo_surface_destroy (rendered_surface);
+
+  gsk_gl_driver_cache_texture (job->driver, &key, texture_id);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      texture_id);
+  gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static guint
+blur_offscreen (GskGLRenderJob       *job,
+                GskGLRenderOffscreen *offscreen,
+                int                   texture_to_blur_width,
+                int                   texture_to_blur_height,
+                float                 blur_radius_x,
+                float                 blur_radius_y)
+{
+  const GskRoundedRect new_clip = GSK_ROUNDED_RECT_INIT (0, 0, texture_to_blur_width, texture_to_blur_height);
+  GskGLRenderTarget *pass1;
+  GskGLRenderTarget *pass2;
+  graphene_matrix_t prev_projection;
+  graphene_rect_t prev_viewport;
+  guint prev_fbo;
+
+  g_assert (blur_radius_x > 0);
+  g_assert (blur_radius_y > 0);
+  g_assert (offscreen->texture_id > 0);
+  g_assert (offscreen->area.x2 > offscreen->area.x);
+  g_assert (offscreen->area.y2 > offscreen->area.y);
+
+  if (!gsk_gl_driver_create_render_target (job->driver,
+                                           MAX (texture_to_blur_width, 1),
+                                           MAX (texture_to_blur_height, 1),
+                                           job->target_format,
+                                           GL_NEAREST, GL_NEAREST,
+                                           &pass1))
+    return 0;
+
+  if (texture_to_blur_width <= 0 || texture_to_blur_height <= 0)
+    return gsk_gl_driver_release_render_target (job->driver, pass1, FALSE);
+
+  if (!gsk_gl_driver_create_render_target (job->driver,
+                                           texture_to_blur_width,
+                                           texture_to_blur_height,
+                                           job->target_format,
+                                           GL_NEAREST, GL_NEAREST,
+                                           &pass2))
+    return gsk_gl_driver_release_render_target (job->driver, pass1, FALSE);
+
+  gsk_gl_render_job_set_viewport (job, &new_clip.bounds, &prev_viewport);
+  gsk_gl_render_job_set_projection_from_rect (job, &new_clip.bounds, &prev_projection);
+  gsk_gl_render_job_set_modelview (job, NULL);
+  gsk_gl_render_job_push_clip (job, &new_clip);
+
+  /* Bind new framebuffer and clear it */
+  prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, pass1->framebuffer_id);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+  /* Begin drawing the first horizontal pass, using offscreen as the
+   * source texture for the program.
+   */
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      offscreen->texture_id);
+  gsk_gl_program_set_uniform1f (job->current_program,
+                                UNIFORM_BLUR_RADIUS, 0,
+                                blur_radius_x);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_BLUR_SIZE, 0,
+                                texture_to_blur_width,
+                                texture_to_blur_height);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_BLUR_DIR, 0,
+                                1, 0);
+  gsk_gl_render_job_draw_coords (job,
+                                 0, 0, texture_to_blur_width, texture_to_blur_height,
+                                 0, 1, 1, 0,
+                                 (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
+  gsk_gl_render_job_end_draw (job);
+
+  /* Bind second pass framebuffer and clear it */
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, pass2->framebuffer_id);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+  /* Draw using blur program with first pass as source texture */
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      pass1->texture_id);
+  gsk_gl_program_set_uniform1f (job->current_program,
+                                UNIFORM_BLUR_RADIUS, 0,
+                                blur_radius_y);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_BLUR_SIZE, 0,
+                                texture_to_blur_width,
+                                texture_to_blur_height);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_BLUR_DIR, 0,
+                                0, 1);
+  gsk_gl_render_job_draw_coords (job,
+                                 0, 0, texture_to_blur_width, texture_to_blur_height,
+                                 0, 1, 1, 0,
+                                 (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
+  gsk_gl_render_job_end_draw (job);
+
+  gsk_gl_render_job_pop_modelview (job);
+  gsk_gl_render_job_pop_clip (job);
+  gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL);
+  gsk_gl_render_job_set_projection (job, &prev_projection);
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+
+  gsk_gl_driver_release_render_target (job->driver, pass1, TRUE);
+
+  return gsk_gl_driver_release_render_target (job->driver, pass2, FALSE);
+}
+
+static void
+blur_node (GskGLRenderJob       *job,
+           GskGLRenderOffscreen *offscreen,
+           const GskRenderNode  *node,
+           float                 blur_radius,
+           float                *min_x,
+           float                *max_x,
+           float                *min_y,
+           float                *max_y)
+{
+  const float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
+  const float half_blur_extra = (blur_extra / 2.0);
+  float scale_x = job->scale_x;
+  float scale_y = job->scale_y;
+  float texture_width;
+  float texture_height;
+
+  g_assert (blur_radius > 0);
+
+  /* Increase texture size for the given blur radius and scale it */
+  texture_width  = ceilf ((node->bounds.size.width  + blur_extra));
+  texture_height = ceilf ((node->bounds.size.height + blur_extra));
+
+  /* Only blur this if the out region has no texture id yet */
+  if (offscreen->texture_id == 0)
+    {
+      const graphene_rect_t bounds = GRAPHENE_RECT_INIT (node->bounds.origin.x - half_blur_extra,
+                                                         node->bounds.origin.y - half_blur_extra,
+                                                         texture_width, texture_height);
+
+      offscreen->bounds = &bounds;
+      offscreen->reset_clip = TRUE;
+      offscreen->force_offscreen = TRUE;
+
+      if (!gsk_gl_render_job_visit_node_with_offscreen (job, node, offscreen))
+        g_assert_not_reached ();
+
+      /* Ensure that we actually got a real texture_id */
+      g_assert (offscreen->texture_id != 0);
+
+      offscreen->texture_id = blur_offscreen (job,
+                                              offscreen,
+                                              texture_width * scale_x,
+                                              texture_height * scale_y,
+                                              blur_radius * scale_x,
+                                              blur_radius * scale_y);
+      init_full_texture_region (offscreen);
+    }
+
+  *min_x = job->offset_x + node->bounds.origin.x - half_blur_extra;
+  *max_x = job->offset_x + node->bounds.origin.x + node->bounds.size.width + half_blur_extra;
+  *min_y = job->offset_y + node->bounds.origin.y - half_blur_extra;
+  *max_y = job->offset_y + node->bounds.origin.y + node->bounds.size.height + half_blur_extra;
+}
+
+#define ATLAS_SIZE 512
+
+static inline void
+gsk_gl_render_job_visit_color_node (GskGLRenderJob      *job,
+                                    const GskRenderNode *node)
+{
+  const GdkRGBA *rgba;
+  guint16 color[4];
+  GskGLProgram *program;
+  GskGLCommandBatch *batch;
+
+  rgba = gsk_color_node_get_color (node);
+  if (RGBA_IS_CLEAR (rgba))
+    return;
+
+  rgba_to_half (rgba, color);
+
+  /* Avoid switching away from the coloring program for
+   * rendering a solid color.
+   */
+  program = CHOOSE_PROGRAM (job, coloring);
+  batch = gsk_gl_command_queue_get_batch (job->command_queue);
+
+  /* Limit the size, or we end up with a coordinate overflow somwhere. */
+  if (node->bounds.size.width < 300 &&
+      node->bounds.size.height < 300 &&
+      batch->any.kind == GSK_GL_COMMAND_KIND_DRAW &&
+      batch->any.program == program->id)
+    {
+      GskGLRenderOffscreen offscreen = {0};
+
+      gsk_gl_render_job_begin_draw (job, program);
+
+      /* The top left few pixels in our atlases are always
+       * solid white, so we can use it here, without
+       * having to choose any particular atlas texture.
+       */
+      offscreen.was_offscreen = FALSE;
+      offscreen.area.x = 1.f / ATLAS_SIZE;
+      offscreen.area.y = 1.f / ATLAS_SIZE;
+      offscreen.area.x2 = 2.f / ATLAS_SIZE;
+      offscreen.area.y2 = 2.f / ATLAS_SIZE;
+
+      gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                   &node->bounds,
+                                                   &offscreen,
+                                                   color);
+
+      gsk_gl_render_job_end_draw (job);
+    }
+  else
+    {
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
+      gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, color);
+      gsk_gl_render_job_end_draw (job);
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_linear_gradient_node (GskGLRenderJob      *job,
+                                              const GskRenderNode *node)
+{
+  const GskColorStop *stops = gsk_linear_gradient_node_get_color_stops (node, NULL);
+  const graphene_point_t *start = gsk_linear_gradient_node_get_start (node);
+  const graphene_point_t *end = gsk_linear_gradient_node_get_end (node);
+  int n_color_stops = gsk_linear_gradient_node_get_n_color_stops (node);
+  gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE;
+  float x1 = job->offset_x + start->x;
+  float x2 = job->offset_x + end->x;
+  float y1 = job->offset_y + start->y;
+  float y2 = job->offset_y + end->y;
+
+  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, linear_gradient));
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_LINEAR_GRADIENT_NUM_COLOR_STOPS, 0,
+                                n_color_stops);
+  gsk_gl_program_set_uniform1fv (job->current_program,
+                                 UNIFORM_LINEAR_GRADIENT_COLOR_STOPS, 0,
+                                 n_color_stops * 5,
+                                 (const float *)stops);
+  gsk_gl_program_set_uniform4f (job->current_program,
+                                UNIFORM_LINEAR_GRADIENT_POINTS, 0,
+                                x1, y1, x2 - x1, y2 - y1);
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_LINEAR_GRADIENT_REPEAT, 0,
+                                repeat);
+  gsk_gl_render_job_draw_rect (job, &node->bounds);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_conic_gradient_node (GskGLRenderJob      *job,
+                                             const GskRenderNode *node)
+{
+  static const float scale = 0.5f * M_1_PI;
+
+  const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, NULL);
+  const graphene_point_t *center = gsk_conic_gradient_node_get_center (node);
+  int n_color_stops = gsk_conic_gradient_node_get_n_color_stops (node);
+  float angle = gsk_conic_gradient_node_get_angle (node);
+  float bias = angle * scale + 2.0f;
+
+  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, conic_gradient));
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_CONIC_GRADIENT_NUM_COLOR_STOPS, 0,
+                                n_color_stops);
+  gsk_gl_program_set_uniform1fv (job->current_program,
+                                 UNIFORM_CONIC_GRADIENT_COLOR_STOPS, 0,
+                                 n_color_stops * 5,
+                                 (const float *)stops);
+  gsk_gl_program_set_uniform4f (job->current_program,
+                                UNIFORM_CONIC_GRADIENT_GEOMETRY, 0,
+                                job->offset_x + center->x,
+                                job->offset_y + center->y,
+                                scale,
+                                bias);
+  gsk_gl_render_job_draw_rect (job, &node->bounds);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_radial_gradient_node (GskGLRenderJob      *job,
+                                              const GskRenderNode *node)
+{
+  int n_color_stops = gsk_radial_gradient_node_get_n_color_stops (node);
+  const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, NULL);
+  const graphene_point_t *center = gsk_radial_gradient_node_get_center (node);
+  float start = gsk_radial_gradient_node_get_start (node);
+  float end = gsk_radial_gradient_node_get_end (node);
+  float hradius = gsk_radial_gradient_node_get_hradius (node);
+  float vradius = gsk_radial_gradient_node_get_vradius (node);
+  gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE;
+  float scale = 1.0f / (end - start);
+  float bias = -start * scale;
+
+  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, radial_gradient));
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_RADIAL_GRADIENT_NUM_COLOR_STOPS, 0,
+                                n_color_stops);
+  gsk_gl_program_set_uniform1fv (job->current_program,
+                                 UNIFORM_RADIAL_GRADIENT_COLOR_STOPS, 0,
+                                 n_color_stops * 5,
+                                 (const float *)stops);
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_RADIAL_GRADIENT_REPEAT, 0,
+                                repeat);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_RADIAL_GRADIENT_RANGE, 0,
+                                scale, bias);
+  gsk_gl_program_set_uniform4f (job->current_program,
+                                UNIFORM_RADIAL_GRADIENT_GEOMETRY, 0,
+                                job->offset_x + center->x,
+                                job->offset_y + center->y,
+                                1.0f / (hradius * job->scale_x),
+                                1.0f / (vradius * job->scale_y));
+  gsk_gl_render_job_draw_rect (job, &node->bounds);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_clipped_child (GskGLRenderJob        *job,
+                                       const GskRenderNode   *child,
+                                       const graphene_rect_t *clip)
+{
+  graphene_rect_t transformed_clip;
+  GskRoundedRect intersection;
+
+  gsk_gl_render_job_transform_bounds (job, clip, &transformed_clip);
+
+  if (job->current_clip->is_rectilinear)
+    {
+      memset (&intersection.corner, 0, sizeof intersection.corner);
+      graphene_rect_intersection (&transformed_clip,
+                                  &job->current_clip->rect.bounds,
+                                  &intersection.bounds);
+
+      gsk_gl_render_job_push_clip (job, &intersection);
+      gsk_gl_render_job_visit_node (job, child);
+      gsk_gl_render_job_pop_clip (job);
+    }
+  else if (intersect_rounded_rectilinear (&transformed_clip,
+                                          &job->current_clip->rect,
+                                          &intersection))
+    {
+      gsk_gl_render_job_push_clip (job, &intersection);
+      gsk_gl_render_job_visit_node (job, child);
+      gsk_gl_render_job_pop_clip (job);
+    }
+  else
+    {
+      GskGLRenderOffscreen offscreen = {0};
+
+      offscreen.bounds = clip;
+      offscreen.force_offscreen = TRUE;
+      offscreen.reset_clip = TRUE;
+      offscreen.do_not_cache = TRUE;
+
+      gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen);
+
+      g_assert (offscreen.texture_id);
+
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          offscreen.texture_id);
+      gsk_gl_render_job_draw_offscreen_rect (job, clip);
+      gsk_gl_render_job_end_draw (job);
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_clip_node (GskGLRenderJob      *job,
+                                   const GskRenderNode *node)
+{
+  const graphene_rect_t *clip = gsk_clip_node_get_clip (node);
+  const GskRenderNode *child = gsk_clip_node_get_child (node);
+
+  gsk_gl_render_job_visit_clipped_child (job, child, clip);
+}
+
+static inline void
+gsk_gl_render_job_visit_rounded_clip_node (GskGLRenderJob      *job,
+                                           const GskRenderNode *node)
+{
+  const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
+  const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node);
+  GskRoundedRect transformed_clip;
+  float scale_x = job->scale_x;
+  float scale_y = job->scale_y;
+  gboolean need_offscreen;
+
+  if (node_is_invisible (child))
+    return;
+
+  gsk_gl_render_job_transform_bounds (job, &clip->bounds, &transformed_clip.bounds);
+
+  for (guint i = 0; i < G_N_ELEMENTS (transformed_clip.corner); i++)
+    {
+      transformed_clip.corner[i].width = clip->corner[i].width * scale_x;
+      transformed_clip.corner[i].height = clip->corner[i].height * scale_y;
+    }
+
+  if (job->current_clip->is_rectilinear)
+    {
+      GskRoundedRect intersected_clip;
+
+      if (intersect_rounded_rectilinear (&job->current_clip->rect.bounds,
+                                         &transformed_clip,
+                                         &intersected_clip))
+        {
+          gsk_gl_render_job_push_clip (job, &intersected_clip);
+          gsk_gl_render_job_visit_node (job, child);
+          gsk_gl_render_job_pop_clip (job);
+          return;
+        }
+    }
+
+  /* After this point we are really working with a new and a current clip
+   * which both have rounded corners.
+   */
+
+  if (job->clip->len <= 1)
+    need_offscreen = FALSE;
+  else if (rounded_inner_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds))
+    need_offscreen = FALSE;
+  else
+    need_offscreen = TRUE;
+
+  if (!need_offscreen)
+    {
+      /* If the new clip entirely contains the current clip, the intersection is simply
+       * the current clip, so we can ignore the new one.
+       */
+      if (rounded_inner_rect_contains_rect (&transformed_clip, &job->current_clip->rect.bounds))
+        {
+          gsk_gl_render_job_visit_node (job, child);
+          return;
+        }
+
+      gsk_gl_render_job_push_clip (job, &transformed_clip);
+      gsk_gl_render_job_visit_node (job, child);
+      gsk_gl_render_job_pop_clip (job);
+    }
+  else
+    {
+      GskGLRenderOffscreen offscreen = {0};
+
+      offscreen.bounds = &node->bounds;
+      offscreen.force_offscreen = TRUE;
+      offscreen.reset_clip = FALSE;
+
+      gsk_gl_render_job_push_clip (job, &transformed_clip);
+      if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+        g_assert_not_reached ();
+      gsk_gl_render_job_pop_clip (job);
+
+      g_assert (offscreen.texture_id);
+
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          offscreen.texture_id);
+      gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+      gsk_gl_render_job_end_draw (job);
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_rect_border_node (GskGLRenderJob      *job,
+                                          const GskRenderNode *node)
+{
+  const GdkRGBA *colors = gsk_border_node_get_colors (node);
+  const float *widths = gsk_border_node_get_widths (node);
+  const graphene_point_t *origin = &node->bounds.origin;
+  const graphene_size_t *size = &node->bounds.size;
+  guint16 color[4];
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
+
+  if (widths[0] > 0)
+    {
+      rgba_to_half (&colors[0], color);
+      gsk_gl_render_job_draw_rect_with_color (job,
+                                              &GRAPHENE_RECT_INIT (origin->x, origin->y, size->width - widths[1], widths[0]),
+                                              color);
+    }
+
+  if (widths[1] > 0)
+    {
+      rgba_to_half (&colors[1], color);
+      gsk_gl_render_job_draw_rect_with_color (job,
+                                              &GRAPHENE_RECT_INIT (origin->x + size->width - widths[1], origin->y, widths[1], size->height - widths[2]),
+                                              color);
+    }
+
+  if (widths[2] > 0)
+    {
+      rgba_to_half (&colors[2], color);
+      gsk_gl_render_job_draw_rect_with_color (job,
+                                              &GRAPHENE_RECT_INIT (origin->x + widths[3], origin->y + size->height - widths[2], size->width - widths[3], widths[2]),
+                                              color);
+    }
+
+  if (widths[3] > 0)
+    {
+      rgba_to_half (&colors[3], color);
+      gsk_gl_render_job_draw_rect_with_color (job,
+                                              &GRAPHENE_RECT_INIT (origin->x, origin->y + widths[0], widths[3], size->height - widths[0]),
+                                              color);
+    }
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_border_node (GskGLRenderJob      *job,
+                                     const GskRenderNode *node)
+{
+  const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node);
+  const GdkRGBA *colors = gsk_border_node_get_colors (node);
+  const float *widths = gsk_border_node_get_widths (node);
+  struct {
+    float w;
+    float h;
+  } sizes[4];
+  float min_x = job->offset_x + node->bounds.origin.x;
+  float min_y = job->offset_y + node->bounds.origin.y;
+  float max_x = min_x + node->bounds.size.width;
+  float max_y = min_y + node->bounds.size.height;
+  GskRoundedRect outline;
+  guint16 color[4];
+
+  memset (sizes, 0, sizeof sizes);
+
+  if (widths[0] > 0)
+    {
+      sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height);
+      sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height);
+    }
+
+  if (widths[1] > 0)
+    {
+      sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width);
+      sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width);
+    }
+
+  if (widths[2] > 0)
+    {
+      sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height);
+      sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height);
+    }
+
+  if (widths[3] > 0)
+    {
+      sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width);
+      sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width);
+    }
+
+  gsk_gl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, border));
+
+  gsk_gl_program_set_uniform4fv (job->current_program,
+                                 UNIFORM_BORDER_WIDTHS, 0,
+                                 1,
+                                 widths);
+  gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                           UNIFORM_BORDER_OUTLINE_RECT, 0,
+                                           &outline);
+
+  if (widths[0] > 0)
+    {
+      GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+      rgba_to_half (&colors[0], color);
+
+      vertices[0] = (GskGLDrawVertex) { .position = { min_x,              min_y              }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[1] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[2] = (GskGLDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+
+      vertices[3] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[4] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[5] = (GskGLDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+    }
+
+  if (widths[1] > 0)
+    {
+      GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+      rgba_to_half (&colors[1], color);
+
+      vertices[0] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[1] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[2] = (GskGLDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+
+      vertices[3] = (GskGLDrawVertex) { .position = { max_x,              max_y              }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[4] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[5] = (GskGLDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+    }
+
+  if (widths[2] > 0)
+    {
+      GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+      rgba_to_half (&colors[2], color);
+
+      vertices[0] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[1] = (GskGLDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[2] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+
+      vertices[3] = (GskGLDrawVertex) { .position = { max_x,              max_y              }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[4] = (GskGLDrawVertex) { .position = { min_x            ,  max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[5] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+    }
+
+  if (widths[3] > 0)
+    {
+      GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+      rgba_to_half (&colors[3], color);
+
+      vertices[0] = (GskGLDrawVertex) { .position = { min_x,              min_y              }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[1] = (GskGLDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[2] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+
+      vertices[3] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[4] = (GskGLDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
+      vertices[5] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
+    }
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+/* A special case for a pattern that occurs frequently with CSS
+ * backgrounds: two sibling nodes, the first of which is a rounded
+ * clip node with a color node as child, and the second one is a
+ * border node, with the same outline as the clip node. We render
+ * this using the filled_border shader.
+ */
+static void
+gsk_gl_render_job_visit_css_background (GskGLRenderJob      *job,
+                                        const GskRenderNode *node,
+                                        const GskRenderNode *node2)
+{
+  const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
+  const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node2);
+  const float *widths = gsk_border_node_get_widths (node2);
+  float min_x = job->offset_x + node2->bounds.origin.x;
+  float min_y = job->offset_y + node2->bounds.origin.y;
+  float max_x = min_x + node2->bounds.size.width;
+  float max_y = min_y + node2->bounds.size.height;
+  GskRoundedRect outline;
+  GskGLDrawVertex *vertices;
+  guint16 color[4];
+  guint16 color2[4];
+
+  if (node_is_invisible (node2))
+    return;
+
+  rgba_to_half (&gsk_border_node_get_colors (node2)[0], color);
+  rgba_to_half (gsk_color_node_get_color (child), color2);
+
+  gsk_gl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border));
+
+  gsk_gl_program_set_uniform4fv (job->current_program,
+                                 UNIFORM_FILLED_BORDER_WIDTHS, 0,
+                                 1,
+                                 widths);
+  gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                           UNIFORM_FILLED_BORDER_OUTLINE_RECT, 0,
+                                           &outline);
+
+  vertices = gsk_gl_command_queue_add_vertices (job->command_queue);
+
+  vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+  vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+  vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+  vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+  vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+  vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+/* Returns TRUE if applying @transform to @bounds
+ * yields an axis-aligned rectangle
+ */
+static gboolean
+result_is_axis_aligned (GskTransform          *transform,
+                        const graphene_rect_t *bounds)
+{
+  graphene_matrix_t m;
+  graphene_quad_t q;
+  graphene_rect_t b;
+  graphene_point_t b1, b2;
+  const graphene_point_t *p;
+
+  gsk_transform_to_matrix (transform, &m);
+  gsk_matrix_transform_rect (&m, bounds, &q);
+  graphene_quad_bounds (&q, &b);
+  graphene_rect_get_top_left (&b, &b1);
+  graphene_rect_get_bottom_right (&b, &b2);
+
+  for (guint i = 0; i < 4; i++)
+    {
+      p = graphene_quad_get_point (&q, i);
+      if (fabs (p->x - b1.x) > FLT_EPSILON && fabs (p->x - b2.x) > FLT_EPSILON)
+        return FALSE;
+      if (fabs (p->y - b1.y) > FLT_EPSILON && fabs (p->y - b2.y) > FLT_EPSILON)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static inline void
+gsk_gl_render_job_visit_transform_node (GskGLRenderJob      *job,
+                                        const GskRenderNode *node)
+{
+  GskTransform *transform = gsk_transform_node_get_transform (node);
+  const GskTransformCategory category = gsk_transform_get_category (transform);
+  const GskRenderNode *child = gsk_transform_node_get_child (node);
+
+  switch (category)
+    {
+    case GSK_TRANSFORM_CATEGORY_IDENTITY:
+      gsk_gl_render_job_visit_node (job, child);
+    break;
+
+    case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
+      {
+        float dx, dy;
+
+        gsk_transform_node_get_translate (node, &dx, &dy);
+        gsk_gl_render_job_offset (job, dx, dy);
+        gsk_gl_render_job_visit_node (job, child);
+        gsk_gl_render_job_offset (job, -dx, -dy);
+      }
+    break;
+
+    case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
+      {
+        gsk_gl_render_job_push_modelview (job, transform);
+        gsk_gl_render_job_visit_node (job, child);
+        gsk_gl_render_job_pop_modelview (job);
+      }
+    break;
+
+    case GSK_TRANSFORM_CATEGORY_2D:
+    case GSK_TRANSFORM_CATEGORY_3D:
+    case GSK_TRANSFORM_CATEGORY_ANY:
+    case GSK_TRANSFORM_CATEGORY_UNKNOWN:
+      if (node_supports_transform (child))
+        {
+          gsk_gl_render_job_push_modelview (job, transform);
+          gsk_gl_render_job_visit_node (job, child);
+          gsk_gl_render_job_pop_modelview (job);
+        }
+      else
+        {
+          GskGLRenderOffscreen offscreen = {0};
+          float sx = 1, sy  = 1;
+
+          offscreen.bounds = &child->bounds;
+          offscreen.force_offscreen = FALSE;
+          offscreen.reset_clip = TRUE;
+
+          if (!result_is_axis_aligned (transform, &child->bounds))
+            offscreen.linear_filter = TRUE;
+
+          if (category == GSK_TRANSFORM_CATEGORY_2D)
+            {
+              graphene_matrix_t m;
+              double a, b, c, d, tx, ty;
+
+              g_assert (transform != NULL);
+              gsk_transform_to_matrix (transform, &m);
+              if (graphene_matrix_to_2d (&m, &a, &b, &c, &d, &tx, &ty))
+                {
+                  sx = sqrt (a * a + b * b);
+                  sy = sqrt (c * c + d * d);
+                }
+              else
+                sx = sy = 1;
+
+              if (sx != 1 || sy != 1)
+                {
+                  GskTransform *scale;
+
+                  scale = gsk_transform_translate (gsk_transform_scale (NULL, sx, sy), &GRAPHENE_POINT_INIT (tx, ty));
+                  gsk_gl_render_job_push_modelview (job, scale);
+                  transform = gsk_transform_transform (gsk_transform_invert (scale), transform);
+                }
+            }
+
+          if (gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+            {
+              /* For non-trivial transforms, we draw everything on a texture and then
+               * draw the texture transformed. */
+              if (transform)
+                gsk_gl_render_job_push_modelview (job, transform);
+
+              gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+              gsk_gl_program_set_uniform_texture (job->current_program,
+                                                  UNIFORM_SHARED_SOURCE, 0,
+                                                  GL_TEXTURE_2D,
+                                                  GL_TEXTURE0,
+                                                  offscreen.texture_id);
+              gsk_gl_render_job_draw_offscreen (job, &child->bounds, &offscreen);
+              gsk_gl_render_job_end_draw (job);
+
+              if (transform)
+                gsk_gl_render_job_pop_modelview (job);
+            }
+
+          if (category == GSK_TRANSFORM_CATEGORY_2D)
+            {
+              if (sx != 1 || sy != 1)
+                {
+                  gsk_gl_render_job_pop_modelview (job);
+                  gsk_transform_unref (transform);
+                }
+            }
+        }
+    break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_unblurred_inset_shadow_node (GskGLRenderJob      *job,
+                                                     const GskRenderNode *node)
+{
+  const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node);
+  GskRoundedRect transformed_outline;
+  guint16 color[4];
+
+  gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
+  gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                           UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
+                                           &transformed_outline);
+  gsk_gl_program_set_uniform1f (job->current_program,
+                                UNIFORM_INSET_SHADOW_SPREAD, 0,
+                                gsk_inset_shadow_node_get_spread (node));
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_INSET_SHADOW_OFFSET, 0,
+                                gsk_inset_shadow_node_get_dx (node),
+                                gsk_inset_shadow_node_get_dy (node));
+  rgba_to_half (gsk_inset_shadow_node_get_color (node), color);
+  gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, color);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob      *job,
+                                                   const GskRenderNode *node)
+{
+  const GskRoundedRect *node_outline = gsk_inset_shadow_node_get_outline (node);
+  float blur_radius = gsk_inset_shadow_node_get_blur_radius (node);
+  float offset_x = gsk_inset_shadow_node_get_dx (node);
+  float offset_y = gsk_inset_shadow_node_get_dy (node);
+  float scale_x = job->scale_x;
+  float scale_y = job->scale_y;
+  float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
+  float half_blur_extra = blur_radius;
+  float texture_width;
+  float texture_height;
+  int blurred_texture_id;
+  GskTextureKey key;
+  GskGLRenderOffscreen offscreen = {0};
+  guint16 color[4];
+
+  g_assert (blur_radius > 0);
+
+  texture_width = ceilf ((node_outline->bounds.size.width + blur_extra) * scale_x);
+  texture_height = ceilf ((node_outline->bounds.size.height + blur_extra) * scale_y);
+
+  key.pointer = node;
+  key.pointer_is_child = FALSE;
+  key.scale_x = scale_x;
+  key.scale_y = scale_y;
+  key.filter = GL_NEAREST;
+
+  blurred_texture_id = gsk_gl_driver_lookup_texture (job->driver, &key);
+
+  if (blurred_texture_id == 0)
+    {
+      float spread = gsk_inset_shadow_node_get_spread (node) + half_blur_extra;
+      GskRoundedRect transformed_outline;
+      GskRoundedRect outline_to_blur;
+      GskGLRenderTarget *render_target;
+      graphene_matrix_t prev_projection;
+      graphene_rect_t prev_viewport;
+      guint prev_fbo;
+
+      /* TODO: In the following code, we have to be careful about where we apply the scale.
+       * We're manually scaling stuff (e.g. the outline) so we can later use texture_width
+       * and texture_height (which are already scaled) as the geometry and keep the modelview
+       * at a scale of 1. That's kinda complicated though... */
+
+      /* Outline of what we actually want to blur later.
+       * Spread grows inside, so we don't need to account for that. But the blur will need
+       * to read outside of the inset shadow, so we need to draw some color in there. */
+      outline_to_blur = *node_outline;
+      gsk_rounded_rect_shrink (&outline_to_blur,
+                               -half_blur_extra,
+                               -half_blur_extra,
+                               -half_blur_extra,
+                               -half_blur_extra);
+
+      /* Fit to our texture */
+      outline_to_blur.bounds.origin.x = 0;
+      outline_to_blur.bounds.origin.y = 0;
+      outline_to_blur.bounds.size.width *= scale_x;
+      outline_to_blur.bounds.size.height *= scale_y;
+
+      for (guint i = 0; i < 4; i ++)
+        {
+          outline_to_blur.corner[i].width *= scale_x;
+          outline_to_blur.corner[i].height *= scale_y;
+        }
+
+      if (!gsk_gl_driver_create_render_target (job->driver,
+                                               texture_width, texture_height,
+                                               get_target_format (job, node),
+                                               GL_NEAREST, GL_NEAREST,
+                                               &render_target))
+        g_assert_not_reached ();
+
+      gsk_gl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
+      gsk_gl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
+      gsk_gl_render_job_set_modelview (job, NULL);
+      gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT (0, 0, texture_width, texture_height));
+
+      prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+      gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+      gsk_gl_render_job_transform_rounded_rect (job, &outline_to_blur, &transformed_outline);
+
+      /* Actual inset shadow outline drawing */
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
+      gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                               UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
+                                               &transformed_outline);
+      gsk_gl_program_set_uniform1f (job->current_program,
+                                    UNIFORM_INSET_SHADOW_SPREAD, 0,
+                                    spread * MAX (scale_x, scale_y));
+      gsk_gl_program_set_uniform2f (job->current_program,
+                                    UNIFORM_INSET_SHADOW_OFFSET, 0,
+                                    offset_x * scale_x,
+                                    offset_y * scale_y);
+      rgba_to_half (gsk_inset_shadow_node_get_color (node), color);
+      gsk_gl_render_job_draw_with_color (job,
+                                         0, 0, texture_width, texture_height,
+                                         color);
+      gsk_gl_render_job_end_draw (job);
+
+      gsk_gl_render_job_pop_modelview (job);
+      gsk_gl_render_job_pop_clip (job);
+      gsk_gl_render_job_set_projection (job, &prev_projection);
+      gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL);
+      gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+
+      offscreen.texture_id = render_target->texture_id;
+      init_full_texture_region (&offscreen);
+
+      blurred_texture_id = blur_offscreen (job,
+                                           &offscreen,
+                                           texture_width,
+                                           texture_height,
+                                           blur_radius * scale_x,
+                                           blur_radius * scale_y);
+
+      gsk_gl_driver_release_render_target (job->driver, render_target, TRUE);
+
+      gsk_gl_driver_cache_texture (job->driver, &key, blurred_texture_id);
+    }
+
+  g_assert (blurred_texture_id != 0);
+
+  /* Blur the rendered unblurred inset shadow */
+  /* Use a clip to cut away the unwanted parts outside of the original outline */
+  {
+    const gboolean needs_clip = !gsk_rounded_rect_is_rectilinear (node_outline);
+    const float tx1 = half_blur_extra * scale_x / texture_width;
+    const float tx2 = 1.0 - tx1;
+    const float ty1 = half_blur_extra * scale_y / texture_height;
+    const float ty2 = 1.0 - ty1;
+
+    if (needs_clip)
+      {
+        GskRoundedRect node_clip;
+
+        gsk_gl_render_job_transform_bounds (job, &node_outline->bounds, &node_clip.bounds);
+
+        for (guint i = 0; i < 4; i ++)
+          {
+            node_clip.corner[i].width = node_outline->corner[i].width * scale_x;
+            node_clip.corner[i].height = node_outline->corner[i].height * scale_y;
+          }
+
+        gsk_gl_render_job_push_clip (job, &node_clip);
+      }
+
+    offscreen.was_offscreen = TRUE;
+    offscreen.area.x = tx1;
+    offscreen.area.y = ty1;
+    offscreen.area.x2 = tx2;
+    offscreen.area.y2 = ty2;
+
+    gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+    gsk_gl_program_set_uniform_texture (job->current_program,
+                                        UNIFORM_SHARED_SOURCE, 0,
+                                        GL_TEXTURE_2D,
+                                        GL_TEXTURE0,
+                                        blurred_texture_id);
+    gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+    gsk_gl_render_job_end_draw (job);
+
+    if (needs_clip)
+      gsk_gl_render_job_pop_clip (job);
+  }
+}
+
+static inline void
+gsk_gl_render_job_visit_unblurred_outset_shadow_node (GskGLRenderJob      *job,
+                                                      const GskRenderNode *node)
+{
+  const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
+  GskRoundedRect transformed_outline;
+  float x = node->bounds.origin.x;
+  float y = node->bounds.origin.y;
+  float w = node->bounds.size.width;
+  float h = node->bounds.size.height;
+  float spread = gsk_outset_shadow_node_get_spread (node);
+  float dx = gsk_outset_shadow_node_get_dx (node);
+  float dy = gsk_outset_shadow_node_get_dy (node);
+  guint16 color[4];
+  const float edge_sizes[] = { // Top, right, bottom, left
+    spread - dy, spread + dx, spread + dy, spread - dx
+  };
+  const float corner_sizes[][2] = { // top left, top right, bottom right, bottom left
+    { outline->corner[0].width + spread - dx, outline->corner[0].height + spread - dy },
+    { outline->corner[1].width + spread + dx, outline->corner[1].height + spread - dy },
+    { outline->corner[2].width + spread + dx, outline->corner[2].height + spread + dy },
+    { outline->corner[3].width + spread - dx, outline->corner[3].height + spread + dy },
+  };
+
+  rgba_to_half (gsk_outset_shadow_node_get_color (node), color);
+
+  gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, unblurred_outset_shadow));
+  gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                           UNIFORM_UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, 0,
+                                           &transformed_outline);
+  gsk_gl_program_set_uniform1f (job->current_program,
+                                UNIFORM_UNBLURRED_OUTSET_SHADOW_SPREAD, 0,
+                                spread);
+  gsk_gl_program_set_uniform2f (job->current_program,
+                                UNIFORM_UNBLURRED_OUTSET_SHADOW_OFFSET, 0,
+                                dx, dy);
+
+  /* Corners... */
+  if (corner_sizes[0][0] > 0 && corner_sizes[0][1] > 0) /* Top left */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x, y, corner_sizes[0][0], corner_sizes[0][1],
+                                       color);
+  if (corner_sizes[1][0] > 0 && corner_sizes[1][1] > 0) /* Top right */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x + w - corner_sizes[1][0], y,
+                                       corner_sizes[1][0], corner_sizes[1][1],
+                                       color);
+  if (corner_sizes[2][0] > 0 && corner_sizes[2][1] > 0) /* Bottom right */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x + w - corner_sizes[2][0], y + h - corner_sizes[2][1],
+                                       corner_sizes[2][0], corner_sizes[2][1],
+                                       color);
+  if (corner_sizes[3][0] > 0 && corner_sizes[3][1] > 0) /* Bottom left */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x, y + h - corner_sizes[3][1],
+                                       corner_sizes[3][0], corner_sizes[3][1],
+                                       color);
+  /* Edges... */;
+  if (edge_sizes[0] > 0) /* Top */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x + corner_sizes[0][0], y,
+                                       w - corner_sizes[0][0] - corner_sizes[1][0], edge_sizes[0],
+                                       color);
+  if (edge_sizes[1] > 0) /* Right */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x + w - edge_sizes[1], y + corner_sizes[1][1],
+                                       edge_sizes[1], h - corner_sizes[1][1] - corner_sizes[2][1],
+                                       color);
+  if (edge_sizes[2] > 0) /* Bottom */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x + corner_sizes[3][0], y + h - edge_sizes[2],
+                                       w - corner_sizes[3][0] - corner_sizes[2][0], edge_sizes[2],
+                                       color);
+  if (edge_sizes[3] > 0) /* Left */
+    gsk_gl_render_job_draw_with_color (job,
+                                       x, y + corner_sizes[0][1],
+                                       edge_sizes[3], h - corner_sizes[0][1] - corner_sizes[3][1],
+                                       color);
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_blurred_outset_shadow_node (GskGLRenderJob      *job,
+                                                    const GskRenderNode *node)
+{
+  const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
+  float scale_x = job->scale_x;
+  float scale_y = job->scale_y;
+  float blur_radius = gsk_outset_shadow_node_get_blur_radius (node);
+  float blur_extra = blur_radius * 2.0f; /* 2.0 = shader radius_multiplier */
+  float half_blur_extra = blur_extra / 2.0f;
+  int extra_blur_pixels_x = ceilf (half_blur_extra * scale_x);
+  int extra_blur_pixels_y = ceilf (half_blur_extra * scale_y);
+  float spread = gsk_outset_shadow_node_get_spread (node);
+  float dx = gsk_outset_shadow_node_get_dx (node);
+  float dy = gsk_outset_shadow_node_get_dy (node);
+  GskRoundedRect scaled_outline;
+  GskRoundedRect transformed_outline;
+  GskGLRenderOffscreen offscreen = {0};
+  int texture_width, texture_height;
+  int blurred_texture_id;
+  int cached_tid;
+  gboolean do_slicing;
+  guint16 color[4];
+  float half_width = outline->bounds.size.width / 2;
+  float half_height = outline->bounds.size.height / 2;
+
+  rgba_to_half (gsk_outset_shadow_node_get_color (node), color);
+
+  /* scaled_outline is the minimal outline we need to draw the given drop shadow,
+   * enlarged by the spread and offset by the blur radius. */
+  scaled_outline = *outline;
+
+  if (outline->bounds.size.width < blur_extra ||
+      outline->bounds.size.height < blur_extra ||
+      outline->corner[0].width >= half_width ||
+      outline->corner[1].width >= half_width ||
+      outline->corner[2].width >= half_width ||
+      outline->corner[3].width >= half_width ||
+      outline->corner[0].height >= half_height ||
+      outline->corner[1].height >= half_height ||
+      outline->corner[2].height >= half_height ||
+      outline->corner[3].height >= half_height)
+    {
+      do_slicing = FALSE;
+      gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread);
+    }
+  else
+    {
+      /* Shrink our outline to the minimum size that can still hold all the border radii */
+      gsk_rounded_rect_shrink_to_minimum (&scaled_outline);
+      /* Increase by the spread */
+      gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread);
+      /* Grow bounds but don't grow corners */
+      graphene_rect_inset (&scaled_outline.bounds, - blur_extra / 2.0, - blur_extra / 2.0);
+      /* For the center part, we add a few pixels */
+      scaled_outline.bounds.size.width += SHADOW_EXTRA_SIZE;
+      scaled_outline.bounds.size.height += SHADOW_EXTRA_SIZE;
+
+      do_slicing = TRUE;
+    }
+
+  texture_width = (int)ceil ((scaled_outline.bounds.size.width + blur_extra) * scale_x);
+  texture_height = (int)ceil ((scaled_outline.bounds.size.height + blur_extra) * scale_y);
+
+  scaled_outline.bounds.origin.x = extra_blur_pixels_x;
+  scaled_outline.bounds.origin.y = extra_blur_pixels_y;
+  scaled_outline.bounds.size.width = texture_width - (extra_blur_pixels_x * 2);
+  scaled_outline.bounds.size.height = texture_height - (extra_blur_pixels_y * 2);
+
+  for (guint i = 0; i < G_N_ELEMENTS (scaled_outline.corner); i++)
+    {
+      scaled_outline.corner[i].width *= scale_x;
+      scaled_outline.corner[i].height *= scale_y;
+    }
+
+  cached_tid = gsk_gl_shadow_library_lookup (job->driver->shadows, &scaled_outline, blur_radius);
+
+  if (cached_tid == 0)
+    {
+      GdkGLContext *context = job->command_queue->context;
+      GskGLRenderTarget *render_target;
+      graphene_matrix_t prev_projection;
+      graphene_rect_t prev_viewport;
+      guint prev_fbo;
+
+      gsk_gl_driver_create_render_target (job->driver,
+                                          texture_width, texture_height,
+                                          get_target_format (job, node),
+                                          GL_NEAREST, GL_NEAREST,
+                                          &render_target);
+
+      if (gdk_gl_context_has_debug (context))
+        {
+          gdk_gl_context_label_object_printf (context,
+                                              GL_TEXTURE,
+                                              render_target->texture_id,
+                                              "Outset Shadow Temp %d",
+                                              render_target->texture_id);
+          gdk_gl_context_label_object_printf (context,
+                                              GL_FRAMEBUFFER,
+                                              render_target->framebuffer_id,
+                                              "Outset Shadow FB Temp %d",
+                                              render_target->framebuffer_id);
+        }
+
+      /* Change state for offscreen */
+      gsk_gl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
+      gsk_gl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
+      gsk_gl_render_job_set_modelview (job, NULL);
+      gsk_gl_render_job_push_clip (job, &scaled_outline);
+
+      /* Bind render target and clear it */
+      prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+      gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+      /* Draw the outline using color program */
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
+      gsk_gl_render_job_draw_with_color (job, 0, 0, texture_width, texture_height,
+                                         (guint16[]){ FP16_ONE, FP16_ONE, FP16_ONE, FP16_ONE });
+      gsk_gl_render_job_end_draw (job);
+
+      /* Reset state from offscreen */
+      gsk_gl_render_job_pop_clip (job);
+      gsk_gl_render_job_pop_modelview (job);
+      gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL);
+      gsk_gl_render_job_set_projection (job, &prev_projection);
+
+      /* Now blur the outline */
+      init_full_texture_region (&offscreen);
+      offscreen.texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE);
+      blurred_texture_id = blur_offscreen (job,
+                                           &offscreen,
+                                           texture_width,
+                                           texture_height,
+                                           blur_radius * scale_x,
+                                           blur_radius * scale_y);
+
+      gsk_gl_shadow_library_insert (job->driver->shadows,
+                                    &scaled_outline,
+                                    blur_radius,
+                                    blurred_texture_id);
+
+      gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+    }
+  else
+    {
+      blurred_texture_id = cached_tid;
+    }
+
+  gsk_gl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
+
+  if (!do_slicing)
+    {
+      float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx);
+      float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy);
+
+      offscreen.was_offscreen = TRUE;
+      offscreen.texture_id = blurred_texture_id;
+      init_full_texture_region (&offscreen);
+
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          blurred_texture_id);
+      gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                               UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0,
+                                               &transformed_outline);
+      gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                   &GRAPHENE_RECT_INIT (min_x,
+                                                                        min_y,
+                                                                        texture_width / scale_x,
+                                                                        texture_height / scale_y),
+                                                   &offscreen,
+                                                   color);
+      gsk_gl_render_job_end_draw (job);
+
+      return;
+    }
+
+  /* slicing */
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      blurred_texture_id);
+  gsk_gl_program_set_uniform_rounded_rect (job->current_program,
+                                           UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0,
+                                           &transformed_outline);
+
+  {
+    float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx);
+    float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy);
+    float max_x = ceilf (outline->bounds.origin.x + outline->bounds.size.width +
+                         half_blur_extra + dx + spread);
+    float max_y = ceilf (outline->bounds.origin.y + outline->bounds.size.height +
+                         half_blur_extra + dy + spread);
+    const GskGLTextureNineSlice *slices;
+    float left_width, center_width, right_width;
+    float top_height, center_height, bottom_height;
+    GskGLTexture *texture;
+
+    texture = gsk_gl_driver_get_texture_by_id (job->driver, blurred_texture_id);
+    slices = gsk_gl_texture_get_nine_slice (texture, &scaled_outline, extra_blur_pixels_x, extra_blur_pixels_y);
+
+    offscreen.was_offscreen = TRUE;
+
+    /* Our texture coordinates MUST be scaled, while the actual vertex coords
+     * MUST NOT be scaled.
+     */
+
+    left_width = slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x;
+    right_width = slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x;
+    center_width = (max_x - min_x) - (left_width + right_width);
+
+    top_height = slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y;
+    bottom_height = slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y;
+    center_height = (max_y - min_y) - (top_height + bottom_height);
+
+    /* Top left */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_LEFT]))
+      {
+        memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_LEFT].area, sizeof offscreen.area);
+        gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                     &GRAPHENE_RECT_INIT (min_x,
+                                                                          min_y,
+                                                                          left_width,
+                                                                          top_height),
+                                                     &offscreen,
+                                                     color);
+      }
+
+    /* Top center */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_CENTER]))
+    {
+      memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_CENTER].area, sizeof offscreen.area);
+      gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                   &GRAPHENE_RECT_INIT (min_x + left_width,
+                                                                        min_y,
+                                                                        center_width,
+                                                                        top_height),
+                                                   &offscreen,
+                                                   color);
+    }
+
+    /* Top right */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_RIGHT]))
+    {
+      memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_RIGHT].area, sizeof offscreen.area);
+      gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                   &GRAPHENE_RECT_INIT (max_x - right_width,
+                                                                        min_y,
+                                                                        right_width,
+                                                                        top_height),
+                                                   &offscreen,
+                                                   color);
+    }
+
+    /* Bottom right */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_RIGHT]))
+    {
+      memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_RIGHT].area, sizeof offscreen.area);
+      gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                   &GRAPHENE_RECT_INIT (max_x - right_width,
+                                                                        max_y - bottom_height,
+                                                                        right_width,
+                                                                        bottom_height),
+                                                   &offscreen,
+                                                   color);
+    }
+
+    /* Bottom left */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_LEFT]))
+      {
+        memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_LEFT].area, sizeof offscreen.area);
+        gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                     &GRAPHENE_RECT_INIT (min_x,
+                                                                          max_y - bottom_height,
+                                                                          left_width,
+                                                                          bottom_height),
+                                                     &offscreen,
+                                                     color);
+      }
+
+    /* Left side */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_LEFT_CENTER]))
+      {
+        memcpy (&offscreen.area, &slices[NINE_SLICE_LEFT_CENTER].area, sizeof offscreen.area);
+        gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                     &GRAPHENE_RECT_INIT (min_x,
+                                                                          min_y + top_height,
+                                                                          left_width,
+                                                                          center_height),
+                                                     &offscreen,
+                                                     color);
+      }
+
+    /* Right side */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_RIGHT_CENTER]))
+      {
+        memcpy (&offscreen.area, &slices[NINE_SLICE_RIGHT_CENTER].area, sizeof offscreen.area);
+        gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                     &GRAPHENE_RECT_INIT (max_x - right_width,
+                                                                          min_y + top_height,
+                                                                          right_width,
+                                                                          center_height),
+                                                     &offscreen,
+                                                     color);
+      }
+
+    /* Bottom side */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_CENTER]))
+      {
+        memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_CENTER].area, sizeof offscreen.area);
+        gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                     &GRAPHENE_RECT_INIT (min_x + left_width,
+                                                                          max_y - bottom_height,
+                                                                          center_width,
+                                                                          bottom_height),
+                                                     &offscreen,
+                                                     color);
+      }
+
+    /* Middle */
+    if (nine_slice_is_visible (&slices[NINE_SLICE_CENTER]))
+      {
+        if (!gsk_rounded_rect_contains_rect (outline, &GRAPHENE_RECT_INIT (min_x + left_width,
+                                                                           min_y + top_height,
+                                                                           center_width,
+                                                                           center_height)))
+          {
+            memcpy (&offscreen.area, &slices[NINE_SLICE_CENTER].area, sizeof offscreen.area);
+            gsk_gl_render_job_draw_offscreen_with_color (job,
+                                                         &GRAPHENE_RECT_INIT (min_x + left_width,
+                                                                              min_y + top_height,
+                                                                              center_width,
+                                                                              center_height),
+                                                         &offscreen,
+                                                         color);
+          }
+      }
+  }
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline gboolean G_GNUC_PURE
+equal_texture_nodes (const GskRenderNode *node1,
+                     const GskRenderNode *node2)
+{
+  if (gsk_render_node_get_node_type (node1) != GSK_TEXTURE_NODE ||
+      gsk_render_node_get_node_type (node2) != GSK_TEXTURE_NODE)
+    return FALSE;
+
+  if (gsk_texture_node_get_texture (node1) !=
+      gsk_texture_node_get_texture (node2))
+    return FALSE;
+
+  return graphene_rect_equal (&node1->bounds, &node2->bounds);
+}
+
+static inline void
+gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob      *job,
+                                         const GskRenderNode *node)
+{
+  const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
+  const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
+  float progress = gsk_cross_fade_node_get_progress (node);
+  GskGLRenderOffscreen offscreen_start = {0};
+  GskGLRenderOffscreen offscreen_end = {0};
+
+  g_assert (progress > 0.0);
+  g_assert (progress < 1.0);
+
+  offscreen_start.force_offscreen = TRUE;
+  offscreen_start.reset_clip = TRUE;
+  offscreen_start.bounds = &node->bounds;
+
+  offscreen_end.force_offscreen = TRUE;
+  offscreen_end.reset_clip = TRUE;
+  offscreen_end.bounds = &node->bounds;
+
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start))
+    {
+      gsk_gl_render_job_visit_node (job, end_node);
+      return;
+    }
+
+  g_assert (offscreen_start.texture_id);
+
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, end_node, &offscreen_end))
+    {
+      float prev_alpha = gsk_gl_render_job_set_alpha (job, job->alpha * progress);
+      gsk_gl_render_job_visit_node (job, start_node);
+      gsk_gl_render_job_set_alpha (job, prev_alpha);
+      return;
+    }
+
+  g_assert (offscreen_end.texture_id);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, cross_fade));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      offscreen_start.texture_id);
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_CROSS_FADE_SOURCE2, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE1,
+                                      offscreen_end.texture_id);
+  gsk_gl_program_set_uniform1f (job->current_program,
+                                UNIFORM_CROSS_FADE_PROGRESS, 0,
+                                progress);
+  gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen_end);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static gboolean
+is_non_branching (const GskRenderNode *node)
+{
+  switch ((int)gsk_render_node_get_node_type (node))
+    {
+    case GSK_COLOR_NODE:
+    case GSK_LINEAR_GRADIENT_NODE:
+    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+    case GSK_RADIAL_GRADIENT_NODE:
+    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
+    case GSK_CONIC_GRADIENT_NODE:
+    case GSK_BORDER_NODE:
+    case GSK_TEXTURE_NODE:
+    case GSK_INSET_SHADOW_NODE:
+    case GSK_OUTSET_SHADOW_NODE:
+    case GSK_TEXT_NODE:
+    case GSK_CAIRO_NODE:
+      return TRUE;
+
+    case GSK_TRANSFORM_NODE:
+      return is_non_branching (gsk_transform_node_get_child (node));
+
+    case GSK_OPACITY_NODE:
+      return is_non_branching (gsk_opacity_node_get_child (node));
+
+    case GSK_COLOR_MATRIX_NODE:
+      return is_non_branching (gsk_color_matrix_node_get_child (node));
+
+    case GSK_CLIP_NODE:
+      return is_non_branching (gsk_clip_node_get_child (node));
+
+    case GSK_ROUNDED_CLIP_NODE:
+      return is_non_branching (gsk_rounded_clip_node_get_child (node));
+
+    case GSK_SHADOW_NODE:
+      return is_non_branching (gsk_shadow_node_get_child (node));
+
+    case GSK_BLUR_NODE:
+      return is_non_branching (gsk_shadow_node_get_child (node));
+
+    case GSK_DEBUG_NODE:
+      return is_non_branching (gsk_debug_node_get_child (node));
+
+    case GSK_CONTAINER_NODE:
+      return gsk_container_node_get_n_children (node) == 1 &&
+             is_non_branching (gsk_container_node_get_child (node, 0));
+
+    default:
+      return FALSE;
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_opacity_node (GskGLRenderJob      *job,
+                                      const GskRenderNode *node)
+{
+  const GskRenderNode *child = gsk_opacity_node_get_child (node);
+  float opacity = gsk_opacity_node_get_opacity (node);
+  float new_alpha = job->alpha * opacity;
+
+  if (!ALPHA_IS_CLEAR (new_alpha))
+    {
+      float prev_alpha = gsk_gl_render_job_set_alpha (job, new_alpha);
+
+      /* Handle a few easy cases without offscreen. We bail out
+       * as soon as we see nodes with multiple children - in theory,
+       * we would only need offscreens for overlapping children.
+       */
+      if (is_non_branching (child))
+        {
+          gsk_gl_render_job_visit_node (job, child);
+          gsk_gl_render_job_set_alpha (job, prev_alpha);
+        }
+      else
+        {
+          GskGLRenderOffscreen offscreen = {0};
+
+          offscreen.bounds = &child->bounds;
+          offscreen.force_offscreen = TRUE;
+          offscreen.reset_clip = TRUE;
+
+          /* Note: offscreen rendering resets alpha to 1.0 */
+          if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+            return;
+
+          g_assert (offscreen.texture_id);
+
+          gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+          gsk_gl_program_set_uniform_texture (job->current_program,
+                                              UNIFORM_SHARED_SOURCE, 0,
+                                              GL_TEXTURE_2D,
+                                              GL_TEXTURE0,
+                                              offscreen.texture_id);
+          gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+          gsk_gl_render_job_end_draw (job);
+        }
+
+      gsk_gl_render_job_set_alpha (job, prev_alpha);
+    }
+}
+
+static inline int
+compute_phase_and_pos (float value, float *pos)
+{
+  float v;
+
+  *pos = floorf (value);
+
+  v = value - *pos;
+
+  if (v < 0.125)
+    return 0;
+  else if (v < 0.375)
+    return 1;
+  else if (v < 0.625)
+    return 2;
+  else if (v < 0.875)
+    return 3;
+  else
+    {
+      *pos += 1;
+      return 0;
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_text_node (GskGLRenderJob      *job,
+                                   const GskRenderNode *node,
+                                   const GdkRGBA       *color,
+                                   gboolean             force_color)
+{
+  const PangoFont *font = gsk_text_node_get_font (node);
+  const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
+  const graphene_point_t *offset = gsk_text_node_get_offset (node);
+  float text_scale = MAX (job->scale_x, job->scale_y); /* TODO: Fix for uneven scales? */
+  guint num_glyphs = gsk_text_node_get_num_glyphs (node);
+  float x = offset->x + job->offset_x;
+  float y = offset->y + job->offset_y;
+  GskGLGlyphLibrary *library = job->driver->glyphs;
+  GskGLCommandBatch *batch;
+  int x_position = 0;
+  GskGLGlyphKey lookup;
+  guint last_texture = 0;
+  GskGLDrawVertex *vertices;
+  guint used = 0;
+  guint16 nc[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE };
+  guint16 cc[4];
+  const guint16 *c;
+  const PangoGlyphInfo *gi;
+  guint i;
+  int yshift;
+  float ypos;
+
+  if (num_glyphs == 0)
+    return;
+
+  if ((force_color || !gsk_text_node_has_color_glyphs (node)) &&
+      RGBA_IS_CLEAR (color))
+    return;
+
+  rgba_to_half (color, cc);
+
+  lookup.font = (PangoFont *)font;
+  lookup.scale = (guint) (text_scale * 1024);
+
+  yshift = compute_phase_and_pos (y, &ypos);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
+
+  batch = gsk_gl_command_queue_get_batch (job->command_queue);
+  vertices = gsk_gl_command_queue_add_n_vertices (job->command_queue, num_glyphs);
+
+  /* We use one quad per character */
+  for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++)
+    {
+      const GskGLGlyphValue *glyph;
+      float glyph_x, glyph_y, glyph_x2, glyph_y2;
+      float tx, ty, tx2, ty2;
+      float cx;
+      float cy;
+      guint texture_id;
+
+      lookup.glyph = gi->glyph;
+
+      /* If the glyph has color, we don't need to recolor anything.
+       * We tell the shader by setting the color to vec4(-1).
+       */
+      if (!force_color && gi->attr.is_color)
+        c = nc;
+      else
+        c = cc;
+
+      cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
+      lookup.xshift = compute_phase_and_pos (x + cx, &cx);
+
+      if G_UNLIKELY (gi->geometry.y_offset != 0)
+        {
+          cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
+          lookup.yshift = compute_phase_and_pos (y + cy, &cy);
+        }
+      else
+        {
+          lookup.yshift = yshift;
+          cy = ypos;
+        }
+
+      x_position += gi->geometry.width;
+
+      texture_id = gsk_gl_glyph_library_lookup_or_add (library, &lookup, &glyph);
+      if G_UNLIKELY (texture_id == 0)
+        continue;
+
+      if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff)
+        {
+          if G_LIKELY (last_texture != 0)
+            {
+              guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count;
+
+              /* Since we have batched added our VBO vertices to avoid repeated
+               * calls to the buffer, we need to manually tweak the vbo offset
+               * of the new batch as otherwise it will point at the end of our
+               * vbo array.
+               */
+              gsk_gl_render_job_split_draw (job);
+              batch = gsk_gl_command_queue_get_batch (job->command_queue);
+              batch->draw.vbo_offset = vbo_offset;
+            }
+
+          gsk_gl_program_set_uniform_texture (job->current_program,
+                                              UNIFORM_SHARED_SOURCE, 0,
+                                              GL_TEXTURE_2D,
+                                              GL_TEXTURE0,
+                                              texture_id);
+          last_texture = texture_id;
+        }
+
+      tx = glyph->entry.area.x;
+      ty = glyph->entry.area.y;
+      tx2 = glyph->entry.area.x2;
+      ty2 = glyph->entry.area.y2;
+
+      glyph_x = cx + glyph->ink_rect.x;
+      glyph_y = cy + glyph->ink_rect.y;
+      glyph_x2 = glyph_x + glyph->ink_rect.width;
+      glyph_y2 = glyph_y + glyph->ink_rect.height;
+
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x,  glyph_y  }, .uv = { tx,  ty  }, .color = { c[0], c[1], c[2], c[3] } };
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x,  glyph_y2 }, .uv = { tx,  ty2 }, .color = { c[0], c[1], c[2], c[3] } };
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y  }, .uv = { tx2, ty  }, .color = { c[0], c[1], c[2], c[3] } };
+
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y2 }, .uv = { tx2, ty2 }, .color = { c[0], c[1], c[2], c[3] } };
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x,  glyph_y2 }, .uv = { tx,  ty2 }, .color = { c[0], c[1], c[2], c[3] } };
+      *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y  }, .uv = { tx2, ty  }, .color = { c[0], c[1], c[2], c[3] } };
+
+      batch->draw.vbo_count += GSK_GL_N_VERTICES;
+      used++;
+    }
+
+  if (used != num_glyphs)
+    gsk_gl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used);
+
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_shadow_node (GskGLRenderJob      *job,
+                                     const GskRenderNode *node)
+{
+  const gsize n_shadows = gsk_shadow_node_get_n_shadows (node);
+  const GskRenderNode *original_child = gsk_shadow_node_get_child (node);
+  const GskRenderNode *shadow_child = original_child;
+
+  /* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact.
+   * If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */
+  if (gsk_render_node_get_node_type (shadow_child) == GSK_COLOR_MATRIX_NODE &&
+      !color_matrix_modifies_alpha (shadow_child))
+    shadow_child = gsk_color_matrix_node_get_child (shadow_child);
+
+  for (guint i = 0; i < n_shadows; i++)
+    {
+      const GskShadow *shadow = gsk_shadow_node_get_shadow (node, i);
+      const float dx = shadow->dx;
+      const float dy = shadow->dy;
+      GskGLRenderOffscreen offscreen = {0};
+      graphene_rect_t bounds;
+      guint16 color[4];
+
+      if (RGBA_IS_CLEAR (&shadow->color))
+        continue;
+
+      if (node_is_invisible (shadow_child))
+        continue;
+
+      if (shadow->radius == 0 &&
+          gsk_render_node_get_node_type (shadow_child) == GSK_TEXT_NODE)
+        {
+          if (dx != 0 || dy != 0)
+            {
+              gsk_gl_render_job_offset (job, dx, dy);
+              gsk_gl_render_job_visit_text_node (job, shadow_child, &shadow->color, TRUE);
+              gsk_gl_render_job_offset (job, -dx, -dy);
+            }
+          continue;
+        }
+
+      if (shadow->radius > 0)
+        {
+          float min_x;
+          float min_y;
+          float max_x;
+          float max_y;
+
+          offscreen.do_not_cache = TRUE;
+
+          blur_node (job,
+                     &offscreen,
+                     shadow_child,
+                     shadow->radius,
+                     &min_x, &max_x,
+                     &min_y, &max_y);
+
+          bounds.origin.x = min_x - job->offset_x;
+          bounds.origin.y = min_y - job->offset_y;
+          bounds.size.width = max_x - min_x;
+          bounds.size.height = max_y - min_y;
+
+          offscreen.was_offscreen = TRUE;
+        }
+      else if (dx == 0 && dy == 0)
+        {
+          continue; /* Invisible anyway */
+        }
+      else
+        {
+          offscreen.bounds = &shadow_child->bounds;
+          offscreen.reset_clip = TRUE;
+          offscreen.do_not_cache = TRUE;
+
+          if (!gsk_gl_render_job_visit_node_with_offscreen (job, shadow_child, &offscreen))
+            g_assert_not_reached ();
+
+          bounds = shadow_child->bounds;
+        }
+
+      gsk_gl_render_job_offset (job, dx, dy);
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          offscreen.texture_id);
+      rgba_to_half (&shadow->color, color);
+      gsk_gl_render_job_draw_offscreen_with_color (job, &bounds, &offscreen, color);
+      gsk_gl_render_job_end_draw (job);
+      gsk_gl_render_job_offset (job, -dx, -dy);
+    }
+
+  /* Now draw the child normally */
+  gsk_gl_render_job_visit_node (job, original_child);
+}
+
+static inline void
+gsk_gl_render_job_visit_blur_node (GskGLRenderJob      *job,
+                                   const GskRenderNode *node)
+{
+  const GskRenderNode *child = gsk_blur_node_get_child (node);
+  float blur_radius = gsk_blur_node_get_radius (node);
+  GskGLRenderOffscreen offscreen = {0};
+  GskTextureKey key;
+  gboolean cache_texture;
+  float min_x;
+  float max_x;
+  float min_y;
+  float max_y;
+
+  g_assert (blur_radius > 0);
+
+  if (node_is_invisible (child))
+    return;
+
+  key.pointer = node;
+  key.pointer_is_child = FALSE;
+  key.scale_x = job->scale_x;
+  key.scale_y = job->scale_y;
+  key.filter = GL_NEAREST;
+
+  offscreen.texture_id = gsk_gl_driver_lookup_texture (job->driver, &key);
+  cache_texture = offscreen.texture_id == 0;
+
+  blur_node (job,
+             &offscreen,
+             child,
+             blur_radius,
+             &min_x, &max_x, &min_y, &max_y);
+
+  g_assert (offscreen.texture_id != 0);
+
+  if (cache_texture)
+    gsk_gl_driver_cache_texture (job->driver, &key, offscreen.texture_id);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      offscreen.texture_id);
+  gsk_gl_render_job_draw_coords (job,
+                                 min_x, min_y, max_x, max_y,
+                                 0, 1, 1, 0,
+                                 (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } );
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_blend_node (GskGLRenderJob      *job,
+                                    const GskRenderNode *node)
+{
+  const GskRenderNode *top_child = gsk_blend_node_get_top_child (node);
+  const GskRenderNode *bottom_child = gsk_blend_node_get_bottom_child (node);
+  GskGLRenderOffscreen top_offscreen = {0};
+  GskGLRenderOffscreen bottom_offscreen = {0};
+
+  top_offscreen.bounds = &node->bounds;
+  top_offscreen.force_offscreen = TRUE;
+  top_offscreen.reset_clip = TRUE;
+
+  bottom_offscreen.bounds = &node->bounds;
+  bottom_offscreen.force_offscreen = TRUE;
+  bottom_offscreen.reset_clip = TRUE;
+
+  /* TODO: We create 2 textures here as big as the blend node, but both the
+   * start and the end node might be a lot smaller than that. */
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, bottom_child, &bottom_offscreen))
+    {
+      gsk_gl_render_job_visit_node (job, top_child);
+      return;
+    }
+
+  g_assert (bottom_offscreen.was_offscreen);
+
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, top_child, &top_offscreen))
+    {
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          bottom_offscreen.texture_id);
+      gsk_gl_render_job_draw_offscreen (job, &node->bounds, &bottom_offscreen);
+      gsk_gl_render_job_end_draw (job);
+      return;
+    }
+
+  g_assert (top_offscreen.was_offscreen);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blend));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      bottom_offscreen.texture_id);
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_BLEND_SOURCE2, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE1,
+                                      top_offscreen.texture_id);
+  gsk_gl_program_set_uniform1i (job->current_program,
+                                UNIFORM_BLEND_MODE, 0,
+                                gsk_blend_node_get_blend_mode (node));
+  gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_color_matrix_node (GskGLRenderJob      *job,
+                                           const GskRenderNode *node)
+{
+  const GskRenderNode *child = gsk_color_matrix_node_get_child (node);
+  GskGLRenderOffscreen offscreen = {0};
+  float offset[4];
+
+  if (node_is_invisible (child))
+    return;
+
+  offscreen.bounds = &node->bounds;
+  offscreen.reset_clip = TRUE;
+
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+    g_assert_not_reached ();
+
+  g_assert (offscreen.texture_id > 0);
+
+  graphene_vec4_to_float (gsk_color_matrix_node_get_color_offset (node), offset);
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color_matrix));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      offscreen.texture_id);
+  gsk_gl_program_set_uniform_matrix (job->current_program,
+                                     UNIFORM_COLOR_MATRIX_COLOR_MATRIX, 0,
+                                     gsk_color_matrix_node_get_color_matrix (node));
+  gsk_gl_program_set_uniform4fv (job->current_program,
+                                 UNIFORM_COLOR_MATRIX_COLOR_OFFSET, 0,
+                                 1,
+                                 offset);
+  gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_gl_shader_node_fallback (GskGLRenderJob      *job,
+                                                 const GskRenderNode *node)
+{
+  guint16 pink[4] = { 15360, 13975, 14758, 15360 }; /* 255 105 180 */
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
+  gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, pink);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_gl_render_job_visit_gl_shader_node (GskGLRenderJob      *job,
+                                        const GskRenderNode *node)
+{
+  GError *error = NULL;
+  GskGLShader *shader;
+  GskGLProgram *program;
+  int n_children;
+
+  shader = gsk_gl_shader_node_get_shader (node);
+  program = gsk_gl_driver_lookup_shader (job->driver, shader, &error);
+  n_children = gsk_gl_shader_node_get_n_children (node);
+
+  if G_UNLIKELY (program == NULL)
+    {
+      if (g_object_get_data (G_OBJECT (shader), "gsk-did-warn") == NULL)
+        {
+          g_object_set_data (G_OBJECT (shader), "gsk-did-warn", GUINT_TO_POINTER (1));
+          g_warning ("Failed to compile gl shader: %s", error->message);
+        }
+      gsk_gl_render_job_visit_gl_shader_node_fallback (job, node);
+      g_clear_error (&error);
+    }
+  else
+    {
+      GskGLRenderOffscreen offscreens[4] = {{0}};
+      const GskGLUniform *uniforms;
+      const guint8 *base;
+      GBytes *args;
+      int n_uniforms;
+
+      g_assert (n_children < G_N_ELEMENTS (offscreens));
+
+      for (guint i = 0; i < n_children; i++)
+        {
+          const GskRenderNode *child = gsk_gl_shader_node_get_child (node, i);
+
+          offscreens[i].bounds = &node->bounds;
+          offscreens[i].force_offscreen = TRUE;
+          offscreens[i].reset_clip = TRUE;
+
+          if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreens[i]))
+            return;
+        }
+
+      args = gsk_gl_shader_node_get_args (node);
+      base = g_bytes_get_data (args, NULL);
+      uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
+
+      gsk_gl_render_job_begin_draw (job, program);
+      for (guint i = 0; i < n_children; i++)
+        gsk_gl_program_set_uniform_texture (program,
+                                            UNIFORM_CUSTOM_TEXTURE1 + i, 0,
+                                            GL_TEXTURE_2D,
+                                            GL_TEXTURE0 + i,
+                                            offscreens[i].texture_id);
+      gsk_gl_program_set_uniform2f (program,
+                                    UNIFORM_CUSTOM_SIZE, 0,
+                                    node->bounds.size.width,
+                                    node->bounds.size.height);
+      for (guint i = 0; i < n_uniforms; i++)
+        {
+          const GskGLUniform *u = &uniforms[i];
+          const guint8 *data = base + u->offset;
+
+          switch (u->type)
+            {
+            default:
+            case GSK_GL_UNIFORM_TYPE_NONE:
+              break;
+            case GSK_GL_UNIFORM_TYPE_FLOAT:
+              gsk_gl_uniform_state_set1fv (job->command_queue->uniforms,
+                                           program->program_info,
+                                           UNIFORM_CUSTOM_ARG0 + i,
+                                           0, 1, (const float *)data);
+              break;
+            case GSK_GL_UNIFORM_TYPE_INT:
+              gsk_gl_uniform_state_set1i (job->command_queue->uniforms,
+                                          program->program_info,
+                                          UNIFORM_CUSTOM_ARG0 + i,
+                                          0, *(const gint32 *)data);
+              break;
+            case GSK_GL_UNIFORM_TYPE_UINT:
+            case GSK_GL_UNIFORM_TYPE_BOOL:
+              gsk_gl_uniform_state_set1ui (job->command_queue->uniforms,
+                                           program->program_info,
+                                           UNIFORM_CUSTOM_ARG0 + i,
+                                           0, *(const guint32 *)data);
+              break;
+            case GSK_GL_UNIFORM_TYPE_VEC2:
+              gsk_gl_uniform_state_set2fv (job->command_queue->uniforms,
+                                           program->program_info,
+                                           UNIFORM_CUSTOM_ARG0 + i,
+                                           0, 1, (const float *)data);
+              break;
+            case GSK_GL_UNIFORM_TYPE_VEC3:
+              gsk_gl_uniform_state_set3fv (job->command_queue->uniforms,
+                                           program->program_info,
+                                           UNIFORM_CUSTOM_ARG0 + i,
+                                           0, 1, (const float *)data);
+              break;
+            case GSK_GL_UNIFORM_TYPE_VEC4:
+              gsk_gl_uniform_state_set4fv (job->command_queue->uniforms,
+                                           program->program_info,
+                                           UNIFORM_CUSTOM_ARG0 + i,
+                                           0, 1, (const float *)data);
+              break;
+            }
+        }
+      gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds);
+      gsk_gl_render_job_end_draw (job);
+    }
+}
+
+static void
+gsk_gl_render_job_upload_texture (GskGLRenderJob       *job,
+                                  GdkTexture           *texture,
+                                  GskGLRenderOffscreen *offscreen)
+{
+  if (gsk_gl_texture_library_can_cache ((GskGLTextureLibrary *)job->driver->icons,
+                                        texture->width,
+                                        texture->height) &&
+      !GDK_IS_GL_TEXTURE (texture))
+    {
+      const GskGLIconData *icon_data;
+
+      gsk_gl_icon_library_lookup_or_add (job->driver->icons, texture, &icon_data);
+      offscreen->texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
+      memcpy (&offscreen->area, &icon_data->entry.area, sizeof offscreen->area);
+    }
+  else
+    {
+      offscreen->texture_id = gsk_gl_driver_load_texture (job->driver, texture, GL_LINEAR, GL_LINEAR);
+      init_full_texture_region (offscreen);
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_texture_node (GskGLRenderJob      *job,
+                                      const GskRenderNode *node)
+{
+  GdkTexture *texture = gsk_texture_node_get_texture (node);
+  int max_texture_size = job->command_queue->max_texture_size;
+
+  if G_LIKELY (texture->width <= max_texture_size &&
+               texture->height <= max_texture_size)
+    {
+      GskGLRenderOffscreen offscreen = {0};
+
+      gsk_gl_render_job_upload_texture (job, texture, &offscreen);
+
+      g_assert (offscreen.texture_id);
+      g_assert (offscreen.was_offscreen == FALSE);
+
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+      gsk_gl_program_set_uniform_texture (job->current_program,
+                                          UNIFORM_SHARED_SOURCE, 0,
+                                          GL_TEXTURE_2D,
+                                          GL_TEXTURE0,
+                                          offscreen.texture_id);
+      gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+      gsk_gl_render_job_end_draw (job);
+    }
+  else
+    {
+      float min_x = job->offset_x + node->bounds.origin.x;
+      float min_y = job->offset_y + node->bounds.origin.y;
+      float max_x = min_x + node->bounds.size.width;
+      float max_y = min_y + node->bounds.size.height;
+      float scale_x = (max_x - min_x) / texture->width;
+      float scale_y = (max_y - min_y) / texture->height;
+      GskGLTextureSlice *slices = NULL;
+      guint n_slices = 0;
+
+      gsk_gl_driver_slice_texture (job->driver, texture, &slices, &n_slices);
+
+      g_assert (slices != NULL);
+      g_assert (n_slices > 0);
+
+      gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+
+      for (guint i = 0; i < n_slices; i ++)
+        {
+          const GskGLTextureSlice *slice = &slices[i];
+          float x1, x2, y1, y2;
+
+          x1 = min_x + (scale_x * slice->rect.x);
+          x2 = x1 + (slice->rect.width * scale_x);
+          y1 = min_y + (scale_y * slice->rect.y);
+          y2 = y1 + (slice->rect.height * scale_y);
+
+          if (i > 0)
+            gsk_gl_render_job_split_draw (job);
+          gsk_gl_program_set_uniform_texture (job->current_program,
+                                              UNIFORM_SHARED_SOURCE, 0,
+                                              GL_TEXTURE_2D,
+                                              GL_TEXTURE0,
+                                              slice->texture_id);
+
+          gsk_gl_render_job_draw_coords (job,
+                                         x1, y1, x2, y2,
+                                         0, 0, 1, 1,
+                                         (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
+        }
+
+      gsk_gl_render_job_end_draw (job);
+    }
+}
+
+static inline void
+gsk_gl_render_job_visit_repeat_node (GskGLRenderJob      *job,
+                                     const GskRenderNode *node)
+{
+  const GskRenderNode *child = gsk_repeat_node_get_child (node);
+  const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node);
+  GskGLRenderOffscreen offscreen = {0};
+
+  if (node_is_invisible (child))
+    return;
+
+  if (!graphene_rect_equal (child_bounds, &child->bounds))
+    {
+      /* TODO: implement these repeat nodes. */
+      gsk_gl_render_job_visit_as_fallback (job, node);
+      return;
+    }
+
+  /* If the size of the repeat node is smaller than the size of the
+   * child node, we don't repeat at all and can just draw that part
+   * of the child texture... */
+  if (rect_contains_rect (child_bounds, &node->bounds))
+    {
+      gsk_gl_render_job_visit_clipped_child (job, child, &node->bounds);
+      return;
+    }
+
+  offscreen.bounds = &child->bounds;
+  offscreen.reset_clip = TRUE;
+
+  if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+    g_assert_not_reached ();
+
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, repeat));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      offscreen.texture_id);
+  gsk_gl_program_set_uniform4f (job->current_program,
+                                UNIFORM_REPEAT_CHILD_BOUNDS, 0,
+                                (node->bounds.origin.x - child_bounds->origin.x) / child_bounds->size.width,
+                                (node->bounds.origin.y - child_bounds->origin.y) / child_bounds->size.height,
+                                node->bounds.size.width / child_bounds->size.width,
+                                node->bounds.size.height / child_bounds->size.height);
+  gsk_gl_program_set_uniform4f (job->current_program,
+                                UNIFORM_REPEAT_TEXTURE_RECT, 0,
+                                offscreen.area.x,
+                                offscreen.was_offscreen ? offscreen.area.y2 : offscreen.area.y,
+                                offscreen.area.x2,
+                                offscreen.was_offscreen ? offscreen.area.y : offscreen.area.y2);
+  gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
+  gsk_gl_render_job_end_draw (job);
+}
+
+static void
+gsk_gl_render_job_visit_node (GskGLRenderJob      *job,
+                              const GskRenderNode *node)
+{
+  gboolean has_clip;
+
+  g_assert (job != NULL);
+  g_assert (node != NULL);
+  g_assert (GSK_IS_GL_DRIVER (job->driver));
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (job->command_queue));
+
+  if (node_is_invisible (node))
+    return;
+
+  if (!gsk_gl_render_job_update_clip (job, &node->bounds, &has_clip))
+    return;
+
+  switch (gsk_render_node_get_node_type (node))
+    {
+    case GSK_BLEND_NODE:
+      gsk_gl_render_job_visit_blend_node (job, node);
+    break;
+
+    case GSK_BLUR_NODE:
+      if (gsk_blur_node_get_radius (node) > 0)
+        gsk_gl_render_job_visit_blur_node (job, node);
+      else
+        gsk_gl_render_job_visit_node (job, gsk_blur_node_get_child (node));
+    break;
+
+    case GSK_BORDER_NODE:
+      if (gsk_border_node_get_uniform_color (node) &&
+          gsk_rounded_rect_is_rectilinear (gsk_border_node_get_outline (node)))
+        gsk_gl_render_job_visit_rect_border_node (job, node);
+      else
+        gsk_gl_render_job_visit_border_node (job, node);
+    break;
+
+    case GSK_CLIP_NODE:
+      gsk_gl_render_job_visit_clip_node (job, node);
+    break;
+
+    case GSK_COLOR_NODE:
+      gsk_gl_render_job_visit_color_node (job, node);
+    break;
+
+    case GSK_COLOR_MATRIX_NODE:
+      gsk_gl_render_job_visit_color_matrix_node (job, node);
+    break;
+
+    case GSK_CONIC_GRADIENT_NODE:
+      if (gsk_conic_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
+        gsk_gl_render_job_visit_conic_gradient_node (job, node);
+      else
+        gsk_gl_render_job_visit_as_fallback (job, node);
+    break;
+
+    case GSK_CONTAINER_NODE:
+      {
+        GskRenderNode **children;
+        guint n_children;
+
+        children = gsk_container_node_get_children (node, &n_children);
+
+        for (guint i = 0; i < n_children; i++)
+          {
+            const GskRenderNode *child = children[i];
+
+            if (i + 1 < n_children &&
+                job->current_clip->is_fully_contained &&
+                gsk_render_node_get_node_type (child) == GSK_ROUNDED_CLIP_NODE)
+              {
+                const GskRenderNode *grandchild = gsk_rounded_clip_node_get_child (child);
+                const GskRenderNode *child2 = children[i + 1];
+                if (gsk_render_node_get_node_type (grandchild) == GSK_COLOR_NODE &&
+                    gsk_render_node_get_node_type (child2) == GSK_BORDER_NODE &&
+                    gsk_border_node_get_uniform_color (child2) &&
+                    rounded_rect_equal (gsk_rounded_clip_node_get_clip (child),
+                                        gsk_border_node_get_outline (child2)))
+                  {
+                    gsk_gl_render_job_visit_css_background (job, child, child2);
+                    i++; /* skip the border node */
+                    continue;
+                  }
+              }
+
+            gsk_gl_render_job_visit_node (job, child);
+          }
+      }
+    break;
+
+    case GSK_CROSS_FADE_NODE:
+      {
+        const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
+        const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
+        float progress = gsk_cross_fade_node_get_progress (node);
+
+        if (progress <= 0.0f)
+          gsk_gl_render_job_visit_node (job, gsk_cross_fade_node_get_start_child (node));
+        else if (progress >= 1.0f || equal_texture_nodes (start_node, end_node))
+          gsk_gl_render_job_visit_node (job, gsk_cross_fade_node_get_end_child (node));
+        else
+          gsk_gl_render_job_visit_cross_fade_node (job, node);
+      }
+    break;
+
+    case GSK_DEBUG_NODE:
+      /* Debug nodes are ignored because draws get reordered anyway */
+      gsk_gl_render_job_visit_node (job, gsk_debug_node_get_child (node));
+    break;
+
+    case GSK_GL_SHADER_NODE:
+      gsk_gl_render_job_visit_gl_shader_node (job, node);
+    break;
+
+    case GSK_INSET_SHADOW_NODE:
+      if (gsk_inset_shadow_node_get_blur_radius (node) > 0)
+        gsk_gl_render_job_visit_blurred_inset_shadow_node (job, node);
+      else
+        gsk_gl_render_job_visit_unblurred_inset_shadow_node (job, node);
+    break;
+
+    case GSK_LINEAR_GRADIENT_NODE:
+    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+      if (gsk_linear_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
+        gsk_gl_render_job_visit_linear_gradient_node (job, node);
+      else
+        gsk_gl_render_job_visit_as_fallback (job, node);
+    break;
+
+    case GSK_OPACITY_NODE:
+      gsk_gl_render_job_visit_opacity_node (job, node);
+    break;
+
+    case GSK_OUTSET_SHADOW_NODE:
+      if (gsk_outset_shadow_node_get_blur_radius (node) > 0)
+        gsk_gl_render_job_visit_blurred_outset_shadow_node (job, node);
+      else
+        gsk_gl_render_job_visit_unblurred_outset_shadow_node (job, node);
+    break;
+
+    case GSK_RADIAL_GRADIENT_NODE:
+    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
+      if (gsk_radial_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
+        gsk_gl_render_job_visit_radial_gradient_node (job, node);
+      else
+        gsk_gl_render_job_visit_as_fallback (job, node);
+    break;
+
+    case GSK_REPEAT_NODE:
+      gsk_gl_render_job_visit_repeat_node (job, node);
+    break;
+
+    case GSK_ROUNDED_CLIP_NODE:
+      gsk_gl_render_job_visit_rounded_clip_node (job, node);
+    break;
+
+    case GSK_SHADOW_NODE:
+      gsk_gl_render_job_visit_shadow_node (job, node);
+    break;
+
+    case GSK_TEXT_NODE:
+      gsk_gl_render_job_visit_text_node (job,
+                                         node,
+                                         gsk_text_node_get_color (node),
+                                         FALSE);
+    break;
+
+    case GSK_TEXTURE_NODE:
+      gsk_gl_render_job_visit_texture_node (job, node);
+    break;
+
+    case GSK_TRANSFORM_NODE:
+      gsk_gl_render_job_visit_transform_node (job, node);
+    break;
+
+    case GSK_CAIRO_NODE:
+      gsk_gl_render_job_visit_as_fallback (job, node);
+    break;
+
+    case GSK_NOT_A_RENDER_NODE:
+    default:
+      g_assert_not_reached ();
+    break;
+    }
+
+  if (has_clip)
+    gsk_gl_render_job_pop_clip (job);
+}
+
+static gboolean
+gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob       *job,
+                                             const GskRenderNode  *node,
+                                             GskGLRenderOffscreen *offscreen)
+{
+  GskTextureKey key;
+  guint cached_id;
+  int filter;
+
+  g_assert (job != NULL);
+  g_assert (node != NULL);
+  g_assert (offscreen != NULL);
+  g_assert (offscreen->texture_id == 0);
+  g_assert (offscreen->bounds != NULL);
+
+  if (node_is_invisible (node))
+    {
+      /* Just to be safe. */
+      offscreen->texture_id = 0;
+      init_full_texture_region (offscreen);
+      offscreen->was_offscreen = FALSE;
+      return FALSE;
+    }
+
+  if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE &&
+      offscreen->force_offscreen == FALSE)
+    {
+      GdkTexture *texture = gsk_texture_node_get_texture (node);
+      gsk_gl_render_job_upload_texture (job, texture, offscreen);
+      g_assert (offscreen->was_offscreen == FALSE);
+      return TRUE;
+    }
+
+  filter = offscreen->linear_filter ? GL_LINEAR : GL_NEAREST;
+
+  /* Check if we've already cached the drawn texture. */
+  key.pointer = node;
+  key.pointer_is_child = TRUE; /* Don't conflict with the child using the cache too */
+  key.parent_rect = *offscreen->bounds;
+  key.scale_x = job->scale_x;
+  key.scale_y = job->scale_y;
+  key.filter = filter;
+
+  cached_id = gsk_gl_driver_lookup_texture (job->driver, &key);
+
+  if (cached_id != 0)
+    {
+      offscreen->texture_id = cached_id;
+      init_full_texture_region (offscreen);
+      /* We didn't render it offscreen, but hand out an offscreen texture id */
+      offscreen->was_offscreen = TRUE;
+      return TRUE;
+    }
+
+  float scaled_width;
+  float scaled_height;
+  float downscale_x = 1;
+  float downscale_y = 1;
+
+  g_assert (job->command_queue->max_texture_size > 0);
+
+  /* Tweak the scale factor so that the required texture doesn't
+   * exceed the max texture limit. This will render with a lower
+   * resolution, but this is better than clipping.
+   */
+  {
+    int max_texture_size = job->command_queue->max_texture_size;
+
+    scaled_width = ceilf (offscreen->bounds->size.width * fabs (job->scale_x));
+    if (scaled_width > max_texture_size)
+      {
+        downscale_x = (float)max_texture_size / scaled_width;
+        scaled_width = max_texture_size;
+      }
+    if (job->scale_x < 0)
+      downscale_x = -downscale_x;
+
+    scaled_height = ceilf (offscreen->bounds->size.height * fabs (job->scale_y));
+    if (scaled_height > max_texture_size)
+      {
+        downscale_y = (float)max_texture_size / scaled_height;
+        scaled_height = max_texture_size;
+      }
+    if (job->scale_y < 0)
+      downscale_y = -downscale_y;
+  }
+
+  GskGLRenderTarget *render_target;
+  graphene_matrix_t prev_projection;
+  graphene_rect_t prev_viewport;
+  graphene_rect_t viewport;
+  float offset_x = job->offset_x;
+  float offset_y = job->offset_y;
+  float prev_alpha;
+  guint prev_fbo;
+
+  if (!gsk_gl_driver_create_render_target (job->driver,
+                                           scaled_width, scaled_height,
+                                           get_target_format (job, node),
+                                           filter, filter,
+                                           &render_target))
+    g_assert_not_reached ();
+
+  if (gdk_gl_context_has_debug (job->command_queue->context))
+    {
+      gdk_gl_context_label_object_printf (job->command_queue->context,
+                                          GL_TEXTURE,
+                                          render_target->texture_id,
+                                          "Offscreen<%s> %d",
+                                          g_type_name_from_instance ((GTypeInstance *) node),
+                                          render_target->texture_id);
+      gdk_gl_context_label_object_printf (job->command_queue->context,
+                                          GL_FRAMEBUFFER,
+                                          render_target->framebuffer_id,
+                                          "Offscreen<%s> FB %d",
+                                          g_type_name_from_instance ((GTypeInstance *) node),
+                                          render_target->framebuffer_id);
+    }
+
+  if (downscale_x != 1 || downscale_y != 1)
+    {
+      GskTransform *transform = gsk_transform_scale (NULL, downscale_x, downscale_y);
+      gsk_gl_render_job_push_modelview (job, transform);
+      gsk_transform_unref (transform);
+    }
+
+  gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport);
+  /* Code above will scale the size with the scale we use in the render ops,
+   * but for the viewport size, we need our own size limited by the texture size */
+  viewport.size.width = scaled_width;
+  viewport.size.height = scaled_height;
+
+  gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport);
+  gsk_gl_render_job_set_projection_from_rect (job, &job->viewport, &prev_projection);
+  prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f);
+
+  prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+  if (offscreen->reset_clip)
+    gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport));
+
+  gsk_gl_render_job_visit_node (job, node);
+
+  if (offscreen->reset_clip)
+    gsk_gl_render_job_pop_clip (job);
+
+  if (downscale_x != 1 || downscale_y != 1)
+    gsk_gl_render_job_pop_modelview (job);
+  gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL);
+  gsk_gl_render_job_set_projection (job, &prev_projection);
+  gsk_gl_render_job_set_alpha (job, prev_alpha);
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+
+  job->offset_x = offset_x;
+  job->offset_y = offset_y;
+
+  offscreen->was_offscreen = TRUE;
+  offscreen->texture_id = gsk_gl_driver_release_render_target (job->driver,
+                                                               render_target,
+                                                               FALSE);
+
+  init_full_texture_region (offscreen);
+
+  if (!offscreen->do_not_cache)
+    gsk_gl_driver_cache_texture (job->driver, &key, offscreen->texture_id);
+
+  return TRUE;
+}
+
+void
+gsk_gl_render_job_render_flipped (GskGLRenderJob *job,
+                                  GskRenderNode  *root)
+{
+  graphene_matrix_t proj;
+  guint framebuffer_id;
+  guint texture_id;
+  guint surface_height;
+
+  g_return_if_fail (job != NULL);
+  g_return_if_fail (root != NULL);
+  g_return_if_fail (GSK_IS_GL_DRIVER (job->driver));
+
+  surface_height = job->viewport.size.height;
+
+  graphene_matrix_init_ortho (&proj,
+                              job->viewport.origin.x,
+                              job->viewport.origin.x + job->viewport.size.width,
+                              job->viewport.origin.y,
+                              job->viewport.origin.y + job->viewport.size.height,
+                              ORTHO_NEAR_PLANE,
+                              ORTHO_FAR_PLANE);
+  graphene_matrix_scale (&proj, 1, -1, 1);
+
+  if (!gsk_gl_command_queue_create_render_target (job->command_queue,
+                                                  MAX (1, job->viewport.size.width),
+                                                  MAX (1, job->viewport.size.height),
+                                                  job->target_format,
+                                                  GL_NEAREST, GL_NEAREST,
+                                                  &framebuffer_id, &texture_id))
+    return;
+
+  /* Setup drawing to our offscreen texture/framebuffer which is flipped */
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, framebuffer_id);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+  /* Visit all nodes creating batches */
+  gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue");
+  gsk_gl_render_job_visit_node (job, root);
+  gdk_gl_context_pop_debug_group (job->command_queue->context);
+
+  /* Now draw to our real destination, but flipped */
+  gsk_gl_render_job_set_alpha (job, 1.0f);
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+  gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
+  gsk_gl_program_set_uniform_texture (job->current_program,
+                                      UNIFORM_SHARED_SOURCE, 0,
+                                      GL_TEXTURE_2D,
+                                      GL_TEXTURE0,
+                                      texture_id);
+  gsk_gl_render_job_draw_rect (job, &job->viewport);
+  gsk_gl_render_job_end_draw (job);
+
+  gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
+  gsk_gl_command_queue_execute (job->command_queue, surface_height, 1, NULL);
+  gdk_gl_context_pop_debug_group (job->command_queue->context);
+
+  glDeleteFramebuffers (1, &framebuffer_id);
+  glDeleteTextures (1, &texture_id);
+}
+
+void
+gsk_gl_render_job_render (GskGLRenderJob *job,
+                          GskRenderNode  *root)
+{
+  G_GNUC_UNUSED gint64 start_time;
+  guint scale_factor;
+  guint surface_height;
+
+  g_return_if_fail (job != NULL);
+  g_return_if_fail (root != NULL);
+  g_return_if_fail (GSK_IS_GL_DRIVER (job->driver));
+
+  scale_factor = MAX (job->scale_x, job->scale_y);
+  surface_height = job->viewport.size.height;
+
+  gsk_gl_command_queue_make_current (job->command_queue);
+
+  /* Build the command queue using the shared GL context for all renderers
+   * on the same display.
+   */
+  start_time = GDK_PROFILER_CURRENT_TIME;
+  gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue");
+  gsk_gl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
+  gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport);
+  gsk_gl_render_job_visit_node (job, root);
+  gdk_gl_context_pop_debug_group (job->command_queue->context);
+  gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Build GL command queue", "");
+
+#if 0
+  /* At this point the atlases have uploaded content while we processed
+   * nodes but have not necessarily been used by the commands in the queue.
+   */
+  gsk_gl_driver_save_atlases_to_png (job->driver, NULL);
+#endif
+
+  /* But now for executing the command queue, we want to use the context
+   * that was provided to us when creating the render job as framebuffer 0
+   * is bound to that context.
+   */
+  start_time = GDK_PROFILER_CURRENT_TIME;
+  gsk_gl_command_queue_make_current (job->command_queue);
+  gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
+  gsk_gl_command_queue_execute (job->command_queue, surface_height, scale_factor, job->region);
+  gdk_gl_context_pop_debug_group (job->command_queue->context);
+  gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Execute GL command queue", "");
+}
+
+void
+gsk_gl_render_job_set_debug_fallback (GskGLRenderJob *job,
+                                      gboolean        debug_fallback)
+{
+  g_return_if_fail (job != NULL);
+
+  job->debug_fallback = !!debug_fallback;
+}
+
+static int
+get_framebuffer_format (guint framebuffer)
+{
+  int size;
+
+  glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
+  glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,  GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &size);
+
+  if (size >= 32)
+    return GL_RGBA32F;
+  else if (size >= 16)
+    return GL_RGBA16F;
+  else
+    return GL_RGBA8;
+}
+
+GskGLRenderJob *
+gsk_gl_render_job_new (GskGLDriver           *driver,
+                       const graphene_rect_t *viewport,
+                       float                  scale_factor,
+                       const cairo_region_t  *region,
+                       guint                  framebuffer)
+{
+  const graphene_rect_t *clip_rect = viewport;
+  graphene_rect_t transformed_extents;
+  GskGLRenderJob *job;
+
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+  g_return_val_if_fail (viewport != NULL, NULL);
+  g_return_val_if_fail (scale_factor > 0, NULL);
+
+  job = g_slice_new0 (GskGLRenderJob);
+  job->driver = g_object_ref (driver);
+  job->command_queue = job->driver->command_queue;
+  job->clip = g_array_sized_new (FALSE, FALSE, sizeof (GskGLRenderClip), 16);
+  job->modelview = g_array_sized_new (FALSE, FALSE, sizeof (GskGLRenderModelview), 16);
+  job->framebuffer = framebuffer;
+  job->offset_x = 0;
+  job->offset_y = 0;
+  job->scale_x = scale_factor;
+  job->scale_y = scale_factor;
+  job->viewport = *viewport;
+  job->target_format = get_framebuffer_format (framebuffer);
+
+  gsk_gl_render_job_set_alpha (job, 1.0f);
+  gsk_gl_render_job_set_projection_from_rect (job, viewport, NULL);
+  gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, scale_factor, scale_factor));
+
+  /* Setup our initial clip. If region is NULL then we are drawing the
+   * whole viewport. Otherwise, we need to convert the region to a
+   * bounding box and clip based on that.
+   */
+
+  if (region != NULL)
+    {
+      cairo_rectangle_int_t extents;
+
+      cairo_region_get_extents (region, &extents);
+      gsk_gl_render_job_transform_bounds (job,
+                                          &GRAPHENE_RECT_INIT (extents.x,
+                                                               extents.y,
+                                                               extents.width,
+                                                               extents.height),
+                                          &transformed_extents);
+      clip_rect = &transformed_extents;
+      job->region = cairo_region_create_rectangle (&extents);
+    }
+
+  gsk_gl_render_job_push_clip (job,
+                               &GSK_ROUNDED_RECT_INIT (clip_rect->origin.x,
+                                                       clip_rect->origin.y,
+                                                       clip_rect->size.width,
+                                                       clip_rect->size.height));
+
+  return job;
+}
+
+void
+gsk_gl_render_job_free (GskGLRenderJob *job)
+{
+  job->current_modelview = NULL;
+  job->current_clip = NULL;
+
+  while (job->modelview->len > 0)
+    {
+      GskGLRenderModelview *modelview = &g_array_index (job->modelview, GskGLRenderModelview, job->modelview->len-1);
+      g_clear_pointer (&modelview->transform, gsk_transform_unref);
+      job->modelview->len--;
+    }
+
+  g_clear_object (&job->driver);
+  g_clear_pointer (&job->region, cairo_region_destroy);
+  g_clear_pointer (&job->modelview, g_array_unref);
+  g_clear_pointer (&job->clip, g_array_unref);
+  g_slice_free (GskGLRenderJob, job);
+}
diff --git a/gsk/gl/gskglrenderjobprivate.h b/gsk/gl/gskglrenderjobprivate.h
new file mode 100644 (file)
index 0000000..4e92b1b
--- /dev/null
@@ -0,0 +1,39 @@
+/* gskglrenderjobprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_RENDER_JOB_H__
+#define __GSK_GL_RENDER_JOB_H__
+
+#include "gskgltypesprivate.h"
+
+GskGLRenderJob *gsk_gl_render_job_new                (GskGLDriver           *driver,
+                                                      const graphene_rect_t *viewport,
+                                                      float                  scale_factor,
+                                                      const cairo_region_t  *region,
+                                                      guint                  framebuffer);
+void            gsk_gl_render_job_free               (GskGLRenderJob        *job);
+void            gsk_gl_render_job_render             (GskGLRenderJob        *job,
+                                                      GskRenderNode         *root);
+void            gsk_gl_render_job_render_flipped     (GskGLRenderJob        *job,
+                                                      GskRenderNode         *root);
+void            gsk_gl_render_job_set_debug_fallback (GskGLRenderJob        *job,
+                                                      gboolean               debug_fallback);
+
+#endif /* __GSK_GL_RENDER_JOB_H__ */
diff --git a/gsk/gl/gskglshadowlibrary.c b/gsk/gl/gskglshadowlibrary.c
new file mode 100644 (file)
index 0000000..86e4e5a
--- /dev/null
@@ -0,0 +1,258 @@
+/* gskglshadowlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gskgldriverprivate.h"
+#include "gskglshadowlibraryprivate.h"
+
+#define MAX_UNUSED_FRAMES (16 * 5)
+
+struct _GskGLShadowLibrary
+{
+  GObject        parent_instance;
+  GskGLDriver *driver;
+  GArray        *shadows;
+};
+
+typedef struct _Shadow
+{
+  GskRoundedRect outline;
+  float          blur_radius;
+  guint          texture_id;
+  gint64         last_used_in_frame;
+} Shadow;
+
+G_DEFINE_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_DRIVER,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GskGLShadowLibrary *
+gsk_gl_shadow_library_new (GskGLDriver *driver)
+{
+  g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL);
+
+  return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY,
+                       "driver", driver,
+                       NULL);
+}
+
+static void
+gsk_gl_shadow_library_dispose (GObject *object)
+{
+  GskGLShadowLibrary *self = (GskGLShadowLibrary *)object;
+
+  for (guint i = 0; i < self->shadows->len; i++)
+    {
+      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
+      gsk_gl_driver_release_texture_by_id (self->driver, shadow->texture_id);
+    }
+
+  g_clear_pointer (&self->shadows, g_array_unref);
+  g_clear_object (&self->driver);
+
+  G_OBJECT_CLASS (gsk_gl_shadow_library_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_shadow_library_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  GskGLShadowLibrary *self = GSK_GL_SHADOW_LIBRARY (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRIVER:
+      g_value_set_object (value, self->driver);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_shadow_library_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  GskGLShadowLibrary *self = GSK_GL_SHADOW_LIBRARY (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRIVER:
+      self->driver = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_shadow_library_class_init (GskGLShadowLibraryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_gl_shadow_library_dispose;
+  object_class->get_property = gsk_gl_shadow_library_get_property;
+  object_class->set_property = gsk_gl_shadow_library_set_property;
+
+  properties [PROP_DRIVER] =
+    g_param_spec_object ("driver",
+                         "Driver",
+                         "Driver",
+                         GSK_TYPE_GL_DRIVER,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gsk_gl_shadow_library_init (GskGLShadowLibrary *self)
+{
+  self->shadows = g_array_new (FALSE, FALSE, sizeof (Shadow));
+}
+
+void
+gsk_gl_shadow_library_insert (GskGLShadowLibrary   *self,
+                              const GskRoundedRect *outline,
+                              float                 blur_radius,
+                              guint                 texture_id)
+{
+  Shadow *shadow;
+
+  g_assert (GSK_IS_GL_SHADOW_LIBRARY (self));
+  g_assert (outline != NULL);
+  g_assert (texture_id != 0);
+
+  gsk_gl_driver_mark_texture_permanent (self->driver, texture_id);
+
+  g_array_set_size (self->shadows, self->shadows->len + 1);
+
+  shadow = &g_array_index (self->shadows, Shadow, self->shadows->len - 1);
+  shadow->outline = *outline;
+  shadow->blur_radius = blur_radius;
+  shadow->texture_id = texture_id;
+  shadow->last_used_in_frame = self->driver->current_frame_id;
+}
+
+guint
+gsk_gl_shadow_library_lookup (GskGLShadowLibrary   *self,
+                              const GskRoundedRect *outline,
+                              float                 blur_radius)
+{
+  Shadow *ret = NULL;
+
+  g_assert (GSK_IS_GL_SHADOW_LIBRARY (self));
+  g_assert (outline != NULL);
+
+  /* Ensure GskRoundedRect  is 12 packed floats without padding
+   * so that we can use memcmp instead of float comparisons.
+   */
+  G_STATIC_ASSERT (sizeof *outline == (sizeof (float) * 12));
+
+  for (guint i = 0; i < self->shadows->len; i++)
+    {
+      Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
+
+      if (blur_radius == shadow->blur_radius &&
+          memcmp (outline, &shadow->outline, sizeof *outline) == 0)
+        {
+          ret = shadow;
+          break;
+        }
+    }
+
+  if (ret == NULL)
+    return 0;
+
+  g_assert (ret->texture_id != 0);
+
+  ret->last_used_in_frame = self->driver->current_frame_id;
+
+  return ret->texture_id;
+}
+
+#if 0
+static void
+write_shadow_to_png (GskGLDriver *driver,
+                     const Shadow *shadow)
+{
+  int width = shadow->outline.bounds.size.width + (shadow->outline.bounds.origin.x * 2);
+  int height = shadow->outline.bounds.size.height + (shadow->outline.bounds.origin.y * 2);
+  char *filename = g_strdup_printf ("shadow_cache_%d_%d_%d.png",
+                                    width, height, shadow->texture_id);
+  GdkTexture *texture;
+
+  texture = gdk_gl_texture_new (gsk_gl_driver_get_context (driver),
+                                shadow->texture_id,
+                                width, height,
+                                NULL, NULL);
+  gdk_texture_save_to_png (texture, filename);
+
+  g_object_unref (texture);
+  g_free (filename);
+}
+#endif
+
+void
+gsk_gl_shadow_library_begin_frame (GskGLShadowLibrary *self)
+{
+  gint64 watermark;
+  int i;
+  int p;
+
+  g_return_if_fail (GSK_IS_GL_SHADOW_LIBRARY (self));
+
+#if 0
+  for (i = 0, p = self->shadows->len; i < p; i++)
+    {
+      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
+      write_shadow_to_png (self->driver, shadow);
+    }
+#endif
+
+  watermark = self->driver->current_frame_id - MAX_UNUSED_FRAMES;
+
+  for (i = 0, p = self->shadows->len; i < p; i++)
+    {
+      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
+
+      if (shadow->last_used_in_frame < watermark)
+        {
+          gsk_gl_driver_release_texture_by_id (self->driver, shadow->texture_id);
+          g_array_remove_index_fast (self->shadows, i);
+          p--;
+          i--;
+        }
+    }
+}
diff --git a/gsk/gl/gskglshadowlibraryprivate.h b/gsk/gl/gskglshadowlibraryprivate.h
new file mode 100644 (file)
index 0000000..a328674
--- /dev/null
@@ -0,0 +1,44 @@
+/* gskglshadowlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_SHADOW_LIBRARY_PRIVATE_H__
+#define __GSK_GL_SHADOW_LIBRARY_PRIVATE_H__
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_gl_shadow_library_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, GSK, GL_SHADOW_LIBRARY, GObject)
+
+GskGLShadowLibrary * gsk_gl_shadow_library_new         (GskGLDriver          *driver);
+void                 gsk_gl_shadow_library_begin_frame (GskGLShadowLibrary   *self);
+guint                gsk_gl_shadow_library_lookup      (GskGLShadowLibrary   *self,
+                                                        const GskRoundedRect *outline,
+                                                        float                 blur_radius);
+void                 gsk_gl_shadow_library_insert      (GskGLShadowLibrary   *self,
+                                                        const GskRoundedRect *outline,
+                                                        float                 blur_radius,
+                                                        guint                 texture_id);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_SHADOW_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/gl/gskgltexture.c b/gsk/gl/gskgltexture.c
new file mode 100644 (file)
index 0000000..6ff56e6
--- /dev/null
@@ -0,0 +1,101 @@
+/* gskgltexture.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdktextureprivate.h>
+
+#include "gskgltextureprivate.h"
+#include "ninesliceprivate.h"
+
+void
+gsk_gl_texture_free (GskGLTexture *texture)
+{
+  if (texture != NULL)
+    {
+      g_assert (texture->link.prev == NULL);
+      g_assert (texture->link.next == NULL);
+
+      if (texture->user)
+        g_clear_pointer (&texture->user, gdk_texture_clear_render_data);
+
+      if (texture->texture_id != 0)
+        {
+          glDeleteTextures (1, &texture->texture_id);
+          texture->texture_id = 0;
+        }
+
+      for (guint i = 0; i < texture->n_slices; i++)
+        {
+          glDeleteTextures (1, &texture->slices[i].texture_id);
+          texture->slices[i].texture_id = 0;
+        }
+
+      g_clear_pointer (&texture->slices, g_free);
+      g_clear_pointer (&texture->nine_slice, g_free);
+
+      g_slice_free (GskGLTexture, texture);
+    }
+}
+
+GskGLTexture *
+gsk_gl_texture_new (guint  texture_id,
+                    int    width,
+                    int    height,
+                    int    format,
+                    int    min_filter,
+                    int    mag_filter,
+                    gint64 frame_id)
+{
+  GskGLTexture *texture;
+
+  texture = g_slice_new0 (GskGLTexture);
+  texture->texture_id = texture_id;
+  texture->link.data = texture;
+  texture->min_filter = min_filter;
+  texture->mag_filter = mag_filter;
+  texture->format = format;
+  texture->width = width;
+  texture->height = height;
+  texture->last_used_in_frame = frame_id;
+
+  return texture;
+}
+
+const GskGLTextureNineSlice *
+gsk_gl_texture_get_nine_slice (GskGLTexture         *texture,
+                               const GskRoundedRect *outline,
+                               float                 extra_pixels_x,
+                               float                 extra_pixels_y)
+{
+  g_assert (texture != NULL);
+  g_assert (outline != NULL);
+
+  if G_UNLIKELY (texture->nine_slice == NULL)
+    {
+      texture->nine_slice = g_new0 (GskGLTextureNineSlice, 9);
+
+      nine_slice_rounded_rect (texture->nine_slice, outline);
+      nine_slice_grow (texture->nine_slice, extra_pixels_x, extra_pixels_y);
+      nine_slice_to_texture_coords (texture->nine_slice, texture->width, texture->height);
+    }
+
+  return texture->nine_slice;
+}
diff --git a/gsk/gl/gskgltexturelibrary.c b/gsk/gl/gskgltexturelibrary.c
new file mode 100644 (file)
index 0000000..3d32c95
--- /dev/null
@@ -0,0 +1,426 @@
+/* gskgltexturelibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gsk/gskdebugprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskgltexturelibraryprivate.h"
+
+#define MAX_FRAME_AGE 60
+
+G_DEFINE_ABSTRACT_TYPE (GskGLTextureLibrary, gsk_gl_texture_library, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_DRIVER,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gsk_gl_texture_library_constructed (GObject *object)
+{
+  G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->constructed (object);
+
+  g_assert (GSK_GL_TEXTURE_LIBRARY (object)->hash_table != NULL);
+}
+
+static void
+gsk_gl_texture_library_dispose (GObject *object)
+{
+  GskGLTextureLibrary *self = (GskGLTextureLibrary *)object;
+
+  g_clear_object (&self->driver);
+
+  G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_texture_library_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRIVER:
+      g_value_set_object (value, self->driver);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_texture_library_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
+
+  switch (prop_id)
+    {
+    case PROP_DRIVER:
+      self->driver = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_texture_library_class_init (GskGLTextureLibraryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = gsk_gl_texture_library_constructed;
+  object_class->dispose = gsk_gl_texture_library_dispose;
+  object_class->get_property = gsk_gl_texture_library_get_property;
+  object_class->set_property = gsk_gl_texture_library_set_property;
+
+  properties [PROP_DRIVER] =
+    g_param_spec_object ("driver",
+                         "Driver",
+                         "Driver",
+                         GSK_TYPE_GL_DRIVER,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gsk_gl_texture_library_init (GskGLTextureLibrary *self)
+{
+}
+
+void
+gsk_gl_texture_library_set_funcs (GskGLTextureLibrary *self,
+                                  GHashFunc            hash_func,
+                                  GEqualFunc           equal_func,
+                                  GDestroyNotify       key_destroy,
+                                  GDestroyNotify       value_destroy)
+{
+  g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
+  g_return_if_fail (self->hash_table == NULL);
+
+  self->hash_table = g_hash_table_new_full (hash_func, equal_func,
+                                            key_destroy, value_destroy);
+}
+
+void
+gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self,
+                                    gint64               frame_id,
+                                    GPtrArray            *removed_atlases)
+{
+  GHashTableIter iter;
+
+  g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
+
+  if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
+    GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases);
+
+  if (removed_atlases != NULL)
+    {
+      GskGLTextureAtlasEntry *entry;
+      guint dropped = 0;
+
+      g_hash_table_iter_init (&iter, self->hash_table);
+      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
+        {
+          if (entry->is_atlased)
+            {
+              for (guint i = 0; i < removed_atlases->len; i++)
+                {
+                  GskGLTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i);
+
+                  if (atlas == entry->atlas)
+                    {
+                      g_hash_table_iter_remove (&iter);
+                      dropped++;
+                      break;
+                    }
+                }
+            }
+        }
+
+      GSK_NOTE (GLYPH_CACHE,
+                if (dropped > 0)
+                  g_message ("%s: Dropped %d items",
+                             G_OBJECT_TYPE_NAME (self), dropped));
+    }
+
+  if (frame_id % MAX_FRAME_AGE == 0)
+    {
+      GskGLTextureAtlasEntry *entry;
+      int atlased = 0;
+      int dropped = 0;
+
+      g_hash_table_iter_init (&iter, self->hash_table);
+      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
+        {
+          if (!entry->is_atlased && !entry->accessed)
+            {
+              gsk_gl_driver_release_texture (self->driver, entry->texture);
+              g_hash_table_iter_remove (&iter);
+              dropped++;
+              continue;
+            }
+
+          gsk_gl_texture_atlas_entry_mark_unused (entry);
+          entry->accessed = FALSE;
+          if (entry->is_atlased)
+            atlased++;
+        }
+
+      GSK_NOTE (GLYPH_CACHE, g_message ("%s: Dropped %d individual items",
+                                        G_OBJECT_TYPE_NAME (self),
+                                        dropped);
+                             g_message ("%s: %d items cached (%d atlased, %d individually)",
+                                        G_OBJECT_TYPE_NAME (self),
+                                        g_hash_table_size (self->hash_table),
+                                        atlased,
+                                        g_hash_table_size (self->hash_table) - atlased));
+    }
+}
+
+static GskGLTexture *
+gsk_gl_texture_library_pack_one (GskGLTextureLibrary *self,
+                                 guint                width,
+                                 guint                height)
+{
+  GskGLTexture *texture;
+
+  g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self));
+
+  if (width > self->driver->command_queue->max_texture_size ||
+      height > self->driver->command_queue->max_texture_size)
+    {
+      g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.",
+                 width, height, self->driver->command_queue->max_texture_size);
+      width = MIN (width, self->driver->command_queue->max_texture_size);
+      height = MIN (height, self->driver->command_queue->max_texture_size);
+    }
+
+  texture = gsk_gl_driver_create_texture (self->driver, width, height, GL_RGBA8, GL_LINEAR, GL_LINEAR);
+  texture->permanent = TRUE;
+
+  return texture;
+}
+
+static inline gboolean
+gsk_gl_texture_atlas_pack (GskGLTextureAtlas *self,
+                           int                width,
+                           int                height,
+                           int               *out_x,
+                           int               *out_y)
+{
+  stbrp_rect rect;
+
+  rect.w = width;
+  rect.h = height;
+
+  stbrp_pack_rects (&self->context, &rect, 1);
+
+  if (rect.was_packed)
+    {
+      *out_x = rect.x;
+      *out_y = rect.y;
+    }
+
+  return rect.was_packed;
+}
+
+static void
+gsk_gl_texture_atlas_initialize (GskGLDriver       *driver,
+                                 GskGLTextureAtlas *atlas)
+{
+  /* Insert a single pixel at 0,0 for use in coloring */
+
+  gboolean packed G_GNUC_UNUSED;
+  int x, y;
+  guint gl_format;
+  guint gl_type;
+  guint8 pixel_data[4 * 3 * 3];
+
+  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                          "Initializing Atlas");
+
+  packed = gsk_gl_texture_atlas_pack (atlas, 3, 3, &x, &y);
+  g_assert (packed);
+  g_assert (x == 0 && y == 0);
+
+  memset (pixel_data, 255, sizeof pixel_data);
+
+  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+    {
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_BYTE;
+    }
+  else
+    {
+      gl_format = GL_BGRA;
+      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+    }
+
+  glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
+
+  glTexSubImage2D (GL_TEXTURE_2D, 0,
+                   0, 0,
+                   3, 3,
+                   gl_format, gl_type,
+                   pixel_data);
+
+  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+
+  driver->command_queue->n_uploads++;
+}
+
+static void
+gsk_gl_texture_atlases_pack (GskGLDriver        *driver,
+                             int                 width,
+                             int                 height,
+                             GskGLTextureAtlas **out_atlas,
+                             int                *out_x,
+                             int                *out_y)
+{
+  GskGLTextureAtlas *atlas = NULL;
+  int x, y;
+
+  for (guint i = 0; i < driver->atlases->len; i++)
+    {
+      atlas = g_ptr_array_index (driver->atlases, i);
+
+      if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
+        break;
+
+      atlas = NULL;
+    }
+
+  if (atlas == NULL)
+    {
+      /* No atlas has enough space, so create a new one... */
+      atlas = gsk_gl_driver_create_atlas (driver);
+
+      gsk_gl_texture_atlas_initialize (driver, atlas);
+
+      /* Pack it onto that one, which surely has enough space... */
+      if (!gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
+        g_assert_not_reached ();
+    }
+
+  *out_atlas = atlas;
+  *out_x = x;
+  *out_y = y;
+}
+
+gpointer
+gsk_gl_texture_library_pack (GskGLTextureLibrary *self,
+                             gpointer             key,
+                             gsize                valuelen,
+                             guint                width,
+                             guint                height,
+                             int                  padding,
+                             guint               *out_packed_x,
+                             guint               *out_packed_y)
+{
+  GskGLTextureAtlasEntry *entry;
+  GskGLTextureAtlas *atlas = NULL;
+
+  g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self));
+  g_assert (key != NULL);
+  g_assert (valuelen > sizeof (GskGLTextureAtlasEntry));
+  g_assert (out_packed_x != NULL);
+  g_assert (out_packed_y != NULL);
+
+  entry = g_slice_alloc0 (valuelen);
+  entry->n_pixels = width * height;
+  entry->accessed = TRUE;
+  entry->used = TRUE;
+
+  /* If our size is invisible then we just want an entry in the
+   * cache for faster lookups, but do not actually spend any texture
+   * allocations on this entry.
+   */
+  if (width <= 0 && height <= 0)
+    {
+      entry->is_atlased = FALSE;
+      entry->texture = NULL;
+      entry->area.x = 0.0f;
+      entry->area.y = 0.0f;
+      entry->area.x2 = 0.0f;
+      entry->area.y2 = 0.0f;
+
+      *out_packed_x = 0;
+      *out_packed_y = 0;
+    }
+  else if (width <= self->max_entry_size && height <= self->max_entry_size)
+    {
+      int packed_x;
+      int packed_y;
+
+      gsk_gl_texture_atlases_pack (self->driver,
+                                   padding + width + padding,
+                                   padding + height + padding,
+                                   &atlas,
+                                   &packed_x,
+                                   &packed_y);
+
+      entry->atlas = atlas;
+      entry->is_atlased = TRUE;
+      entry->area.x = (packed_x + padding) / (float)atlas->width;
+      entry->area.y = (packed_y + padding) / (float)atlas->height;
+      entry->area.x2 = (packed_x + padding + width) / (float)atlas->width;
+      entry->area.y2 = (packed_y + padding + height) / (float)atlas->height;
+
+      *out_packed_x = packed_x;
+      *out_packed_y = packed_y;
+    }
+  else
+    {
+      GskGLTexture *texture = gsk_gl_texture_library_pack_one (self,
+                                                               padding + width + padding,
+                                                               padding + height + padding);
+
+      entry->texture = texture;
+      entry->is_atlased = FALSE;
+      entry->accessed = TRUE;
+      entry->area.x = padding / (float) (padding + width + padding);
+      entry->area.y = padding / (float) (padding + height + padding);
+      entry->area.x2 = (padding + width) / (float) (padding + width + padding);
+      entry->area.y2 = (padding + height) / (float) (padding + height + padding);
+
+      *out_packed_x = 0;
+      *out_packed_y = 0;
+    }
+
+  g_hash_table_insert (self->hash_table, key, entry);
+
+  return entry;
+}
diff --git a/gsk/gl/gskgltexturelibraryprivate.h b/gsk/gl/gskgltexturelibraryprivate.h
new file mode 100644 (file)
index 0000000..a784449
--- /dev/null
@@ -0,0 +1,208 @@
+/* gskgltexturelibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_TEXTURE_LIBRARY_PRIVATE_H__
+#define __GSK_GL_TEXTURE_LIBRARY_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+#include "gskgltextureprivate.h"
+
+#include "stb_rect_pack.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_TEXTURE_LIBRARY            (gsk_gl_texture_library_get_type ())
+#define GSK_GL_TEXTURE_LIBRARY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibrary))
+#define GSK_IS_GL_TEXTURE_LIBRARY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY))
+#define GSK_GL_TEXTURE_LIBRARY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibraryClass))
+#define GSK_IS_GL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY))
+#define GSK_GL_TEXTURE_LIBRARY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibraryClass))
+
+typedef struct _GskGLTextureAtlas
+{
+  struct stbrp_context context;
+  struct stbrp_node *nodes;
+
+  int width;
+  int height;
+
+  guint texture_id;
+
+  /* Pixels of rects that have been used at some point,
+   * But are now unused.
+   */
+  int unused_pixels;
+
+  void *user_data;
+} GskGLTextureAtlas;
+
+typedef struct _GskGLTextureAtlasEntry
+{
+  /* A backreference to either the atlas or texture containing
+   * the contents of the atlas entry. For larger items, no atlas
+   * is used and instead a direct texture.
+   */
+  union {
+    GskGLTextureAtlas *atlas;
+    GskGLTexture *texture;
+  };
+
+  /* The area within the atlas translated to 0..1 bounds */
+  struct {
+    float x;
+    float y;
+    float x2;
+    float y2;
+  } area;
+
+  /* Number of pixels in the entry, used to calculate usage
+   * of an atlas while processing.
+   */
+  guint n_pixels : 29;
+
+  /* If entry has marked pixels as used in the atlas this frame */
+  guint used : 1;
+
+  /* If entry was accessed this frame */
+  guint accessed : 1;
+
+  /* When true, backref is an atlas, otherwise texture */
+  guint is_atlased : 1;
+} GskGLTextureAtlasEntry;
+
+typedef struct _GskGLTextureLibrary
+{
+  GObject        parent_instance;
+  GskGLDriver *driver;
+  GHashTable    *hash_table;
+  guint          max_entry_size;
+} GskGLTextureLibrary;
+
+typedef struct _GskGLTextureLibraryClass
+{
+  GObjectClass parent_class;
+
+  void (*begin_frame) (GskGLTextureLibrary *library,
+                       gint64               frame_id,
+                       GPtrArray           *removed_atlases);
+} GskGLTextureLibraryClass;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskGLTextureLibrary, g_object_unref)
+
+GType    gsk_gl_texture_library_get_type    (void) G_GNUC_CONST;
+void     gsk_gl_texture_library_set_funcs   (GskGLTextureLibrary *self,
+                                             GHashFunc            hash_func,
+                                             GEqualFunc           equal_func,
+                                             GDestroyNotify       key_destroy,
+                                             GDestroyNotify       value_destroy);
+void     gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self,
+                                             gint64               frame_id,
+                                             GPtrArray           *removed_atlases);
+gpointer gsk_gl_texture_library_pack        (GskGLTextureLibrary *self,
+                                             gpointer             key,
+                                             gsize                valuelen,
+                                             guint                width,
+                                             guint                height,
+                                             int                  padding,
+                                             guint               *out_packed_x,
+                                             guint               *out_packed_y);
+
+static inline void
+gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self,
+                                  int                n_pixels)
+{
+  g_assert (n_pixels >= 0);
+
+  self->unused_pixels += n_pixels;
+}
+
+static inline void
+gsk_gl_texture_atlas_entry_mark_used (GskGLTextureAtlasEntry *entry)
+{
+  if (entry->used == TRUE || entry->is_atlased == FALSE)
+    return;
+
+  entry->atlas->unused_pixels -= entry->n_pixels;
+  entry->used = TRUE;
+}
+
+static inline void
+gsk_gl_texture_atlas_entry_mark_unused (GskGLTextureAtlasEntry *entry)
+{
+  if (entry->used == FALSE || entry->is_atlased == FALSE)
+    return;
+
+  entry->atlas->unused_pixels += entry->n_pixels;
+  entry->used = FALSE;
+}
+
+static inline gboolean
+gsk_gl_texture_library_lookup (GskGLTextureLibrary     *self,
+                               gconstpointer            key,
+                               GskGLTextureAtlasEntry **out_entry)
+{
+  GskGLTextureAtlasEntry *entry = g_hash_table_lookup (self->hash_table, key);
+
+  if G_LIKELY (entry != NULL && entry->accessed && entry->used)
+    {
+      *out_entry = entry;
+      return TRUE;
+    }
+
+  if (entry != NULL)
+    {
+      gsk_gl_texture_atlas_entry_mark_used (entry);
+      entry->accessed = TRUE;
+      *out_entry = entry;
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static inline guint
+GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (gconstpointer d)
+{
+  const GskGLTextureAtlasEntry *e = d;
+
+  return e->is_atlased ? e->atlas->texture_id
+                       : e->texture ? e->texture->texture_id : 0;
+}
+
+static inline double
+gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
+{
+  if (self->unused_pixels > 0)
+    return (double)(self->unused_pixels) / (double)(self->width * self->height);
+  return 0.0;
+}
+
+static inline gboolean
+gsk_gl_texture_library_can_cache (GskGLTextureLibrary *self,
+                                  int                  width,
+                                  int                  height)
+{
+  g_assert (self->max_entry_size > 0);
+  return width <= self->max_entry_size && height <= self->max_entry_size;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_GL_TEXTURE_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/gl/gskgltextureprivate.h b/gsk/gl/gskgltextureprivate.h
new file mode 100644 (file)
index 0000000..c8605ce
--- /dev/null
@@ -0,0 +1,91 @@
+/* gskgltextureprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef _GSK_GL_TEXTURE_PRIVATE_H__
+#define _GSK_GL_TEXTURE_PRIVATE_H__
+
+#include "gskgltypesprivate.h"
+
+G_BEGIN_DECLS
+
+struct _GskGLTextureSlice
+{
+  cairo_rectangle_int_t rect;
+  guint texture_id;
+};
+
+struct _GskGLTextureNineSlice
+{
+  cairo_rectangle_int_t rect;
+  struct {
+    float x;
+    float y;
+    float x2;
+    float y2;
+  } area;
+};
+
+struct _GskGLTexture
+{
+  /* Used to insert into queue */
+  GList link;
+
+  /* Identifier of the frame that created it */
+  gint64 last_used_in_frame;
+
+  /* Backpointer to texture (can be cleared asynchronously) */
+  GdkTexture *user;
+
+  /* Only used by nine-slice textures */
+  GskGLTextureNineSlice *nine_slice;
+
+  /* Only used by sliced textures */
+  GskGLTextureSlice *slices;
+  guint n_slices;
+
+  /* The actual GL texture identifier in some shared context */
+  guint texture_id;
+
+  int width;
+  int height;
+  int min_filter;
+  int mag_filter;
+  int format;
+
+  /* Set when used by an atlas so we don't drop the texture */
+  guint              permanent : 1;
+};
+
+GskGLTexture                * gsk_gl_texture_new            (guint                 texture_id,
+                                                             int                   width,
+                                                             int                   height,
+                                                             int                   format,
+                                                             int                   min_filter,
+                                                             int                   mag_filter,
+                                                             gint64                frame_id);
+const GskGLTextureNineSlice * gsk_gl_texture_get_nine_slice (GskGLTexture         *texture,
+                                                             const GskRoundedRect *outline,
+                                                             float                 extra_pixels_x,
+                                                             float                 extra_pixels_y);
+void                          gsk_gl_texture_free           (GskGLTexture         *texture);
+
+G_END_DECLS
+
+#endif /* _GSK_GL_TEXTURE_PRIVATE_H__ */
diff --git a/gsk/gl/gskgltypesprivate.h b/gsk/gl/gskgltypesprivate.h
new file mode 100644 (file)
index 0000000..1250756
--- /dev/null
@@ -0,0 +1,66 @@
+/* gskgltypesprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_GL_TYPES_PRIVATE_H__
+#define __GSK_GL_TYPES_PRIVATE_H__
+
+#include <epoxy/gl.h>
+#include <graphene.h>
+#include <gdk/gdk.h>
+#include <gsk/gsk.h>
+
+G_BEGIN_DECLS
+
+#define GSK_GL_N_VERTICES 6
+
+typedef struct _GskGLAttachmentState GskGLAttachmentState;
+typedef struct _GskGLBuffer GskGLBuffer;
+typedef struct _GskGLCommandQueue GskGLCommandQueue;
+typedef struct _GskGLCompiler GskGLCompiler;
+typedef struct _GskGLDrawVertex GskGLDrawVertex;
+typedef struct _GskGLRenderTarget GskGLRenderTarget;
+typedef struct _GskGLGlyphLibrary GskGLGlyphLibrary;
+typedef struct _GskGLIconLibrary GskGLIconLibrary;
+typedef struct _GskGLProgram GskGLProgram;
+typedef struct _GskGLRenderJob GskGLRenderJob;
+typedef struct _GskGLShadowLibrary GskGLShadowLibrary;
+typedef struct _GskGLTexture GskGLTexture;
+typedef struct _GskGLTextureSlice GskGLTextureSlice;
+typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
+typedef struct _GskGLTextureLibrary GskGLTextureLibrary;
+typedef struct _GskGLTextureNineSlice GskGLTextureNineSlice;
+typedef struct _GskGLUniformInfo GskGLUniformInfo;
+typedef struct _GskGLUniformProgram GskGLUniformProgram;
+typedef struct _GskGLUniformState GskGLUniformState;
+typedef struct _GskGLDriver GskGLDriver;
+
+struct _GskGLDrawVertex
+{
+  float position[2];
+  union {
+    float uv[2];
+    guint16 color2[4];
+  };
+  guint16 color[4];
+};
+
+G_END_DECLS
+
+#endif /* __GSK_GL_TYPES_PRIVATE_H__ */
diff --git a/gsk/gl/gskgluniformstate.c b/gsk/gl/gskgluniformstate.c
new file mode 100644 (file)
index 0000000..77cd45a
--- /dev/null
@@ -0,0 +1,269 @@
+/* gskgluniformstate.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gsk/gskroundedrectprivate.h>
+#include <string.h>
+
+#include "gskgluniformstateprivate.h"
+
+static const guint8 uniform_sizes[] = {
+  0,
+
+  sizeof (Uniform1f),
+  sizeof (Uniform2f),
+  sizeof (Uniform3f),
+  sizeof (Uniform4f),
+
+  sizeof (Uniform1f),
+  sizeof (Uniform2f),
+  sizeof (Uniform3f),
+  sizeof (Uniform4f),
+
+  sizeof (Uniform1i),
+  sizeof (Uniform2i),
+  sizeof (Uniform3i),
+  sizeof (Uniform4i),
+
+  sizeof (Uniform1ui),
+
+  sizeof (guint),
+
+  sizeof (graphene_matrix_t),
+  sizeof (GskRoundedRect),
+  sizeof (GdkRGBA),
+
+  0,
+};
+
+GskGLUniformState *
+gsk_gl_uniform_state_new (void)
+{
+  GskGLUniformState *state;
+
+  state = g_atomic_rc_box_new0 (GskGLUniformState);
+  state->programs = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+  state->values_len = 4096;
+  state->values_pos = 0;
+  state->values_buf = g_malloc (4096);
+
+  memset (state->apply_hash, 0, sizeof state->apply_hash);
+
+  return g_steal_pointer (&state);
+}
+
+GskGLUniformState *
+gsk_gl_uniform_state_ref (GskGLUniformState *state)
+{
+  return g_atomic_rc_box_acquire (state);
+}
+
+static void
+gsk_gl_uniform_state_finalize (gpointer data)
+{
+  GskGLUniformState *state = data;
+
+  g_clear_pointer (&state->programs, g_hash_table_unref);
+  g_clear_pointer (&state->values_buf, g_free);
+}
+
+void
+gsk_gl_uniform_state_unref (GskGLUniformState *state)
+{
+  g_atomic_rc_box_release_full (state, gsk_gl_uniform_state_finalize);
+}
+
+gpointer
+gsk_gl_uniform_state_init_value (GskGLUniformState    *state,
+                                 GskGLUniformProgram  *program,
+                                 GskGLUniformFormat    format,
+                                 guint                 array_count,
+                                 guint                 key,
+                                 GskGLUniformMapping **infoptr)
+{
+  GskGLUniformMapping *mapping;
+  guint offset;
+
+  g_assert (state != NULL);
+  g_assert (array_count < 32);
+  g_assert ((int)format >= 0 && format < GSK_GL_UNIFORM_FORMAT_LAST);
+  g_assert (format > 0);
+  g_assert (program != NULL);
+  g_assert (key < program->n_mappings);
+
+  mapping = &program->mappings[key];
+
+  if (mapping->location == -1)
+    {
+      *infoptr = NULL;
+      return NULL;
+    }
+
+  if G_LIKELY (format == mapping->info.format)
+    {
+      if G_LIKELY (array_count <= mapping->info.array_count)
+        {
+          *infoptr = mapping;
+          return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
+        }
+
+      /* We found the uniform, but there is not enough space for the
+       * amount that was requested. Instead, allocate new space and
+       * set the value to "initial" so that the caller just writes
+       * over the previous value.
+       *
+       * This can happen when using dynamic array lengths like the
+       * "n_color_stops" in gradient shaders.
+       */
+      goto setup_info;
+    }
+  else if (mapping->info.format == 0)
+    {
+      goto setup_info;
+    }
+  else
+    {
+      g_critical ("Attempt to access uniform with different type of value "
+                  "than it was initialized with. Program %u Location %u. "
+                  "Was %d now %d (array length %d now %d).",
+                  program->program_id, key, mapping->info.format, format,
+                  mapping->info.array_count, array_count);
+      *infoptr = NULL;
+      return NULL;
+    }
+
+setup_info:
+
+  gsk_gl_uniform_state_realloc (state,
+                                uniform_sizes[format] * MAX (1, array_count),
+                                &offset);
+
+  /* we have 21 bits for offset */
+  g_assert (offset < (1 << GSK_GL_UNIFORM_OFFSET_BITS));
+
+  mapping->info.format = format;
+  mapping->info.offset = offset;
+  mapping->info.array_count = array_count;
+  mapping->info.initial = TRUE;
+  mapping->stamp = 0;
+
+  *infoptr = mapping;
+
+  return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
+}
+
+void
+gsk_gl_uniform_state_end_frame (GskGLUniformState *state)
+{
+  GHashTableIter iter;
+  GskGLUniformProgram *program;
+  guint allocator = 0;
+
+  g_return_if_fail (state != NULL);
+
+  /* After a frame finishes, we want to remove all our copies of uniform
+   * data that isn't needed any longer. Since we treat it as uninitialized
+   * after this frame (to reset it on first use next frame) we can just
+   * discard it but keep an allocation around to reuse.
+   */
+
+  g_hash_table_iter_init (&iter, state->programs);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&program))
+    {
+      for (guint j = 0; j < program->n_mappings; j++)
+        {
+          GskGLUniformMapping *mapping = &program->mappings[j];
+          guint size;
+
+          /* Skip unused uniform mappings */
+          if (mapping->info.format == 0 || mapping->location == -1)
+            continue;
+
+          /* Calculate how much size is needed for the uniform, including arrays */
+          size = uniform_sizes[mapping->info.format] * MAX (1, mapping->info.array_count);
+
+          /* Adjust alignment for value */
+          allocator += gsk_gl_uniform_state_align (allocator, size);
+
+          /* Offset is in slots of 4 bytes */
+          mapping->info.offset = allocator / 4;
+          mapping->info.initial = TRUE;
+          mapping->stamp = 0;
+
+          /* Now advance for this items data */
+          allocator += size;
+        }
+    }
+
+  state->values_pos = allocator;
+
+  /* It can happen that our space requirements grow due to
+   * difference in order increasing padding. As a pragmatic
+   * solution to this, just increase the allocation to cover
+   * the predefined mappins.
+   */
+  if (allocator > state->values_len)
+    {
+      while (allocator > state->values_len)
+        state->values_len *= 2;
+      state->values_buf = g_realloc (state->values_buf, state->values_len);
+    }
+
+  memset (state->apply_hash, 0, sizeof state->apply_hash);
+}
+
+gsize
+gsk_gl_uniform_format_size (GskGLUniformFormat format)
+{
+  g_assert (format > 0);
+  g_assert (format < GSK_GL_UNIFORM_FORMAT_LAST);
+
+  return uniform_sizes[format];
+}
+
+GskGLUniformProgram *
+gsk_gl_uniform_state_get_program (GskGLUniformState         *state,
+                                  guint                      program,
+                                  const GskGLUniformMapping *mappings,
+                                  guint                      n_mappings)
+{
+  GskGLUniformProgram *ret;
+
+  g_return_val_if_fail (state != NULL, NULL);
+  g_return_val_if_fail (program > 0, NULL);
+  g_return_val_if_fail (program < G_MAXUINT, NULL);
+  g_return_val_if_fail (n_mappings <= G_N_ELEMENTS (ret->mappings), NULL);
+
+  ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program));
+
+  if (ret == NULL)
+    {
+      ret = g_new0 (GskGLUniformProgram, 1);
+      ret->program_id = program;
+      ret->n_mappings = n_mappings;
+
+      memcpy (ret->mappings, mappings, n_mappings * sizeof *mappings);
+
+      g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret);
+    }
+
+  return ret;
+}
diff --git a/gsk/gl/gskgluniformstateprivate.h b/gsk/gl/gskgluniformstateprivate.h
new file mode 100644 (file)
index 0000000..af5c82a
--- /dev/null
@@ -0,0 +1,836 @@
+/* gskgluniformstateprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef GSK_GL_UNIFORM_STATE_PRIVATE_H
+#define GSK_GL_UNIFORM_STATE_PRIVATE_H
+
+#include "gskgltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct { float v0; } Uniform1f;
+typedef struct { float v0; float v1; } Uniform2f;
+typedef struct { float v0; float v1; float v2; } Uniform3f;
+typedef struct { float v0; float v1; float v2; float v3; } Uniform4f;
+
+typedef struct { int v0; } Uniform1i;
+typedef struct { int v0; int v1; } Uniform2i;
+typedef struct { int v0; int v1; int v2; } Uniform3i;
+typedef struct { int v0; int v1; int v2; int v3; } Uniform4i;
+
+typedef struct { guint v0; } Uniform1ui;
+
+#define GSK_GL_UNIFORM_ARRAY_BITS  5
+#define GSK_GL_UNIFORM_FORMAT_BITS 5
+#define GSK_GL_UNIFORM_OFFSET_BITS 21
+
+typedef struct _GskGLUniformInfo
+{
+  guint initial     : 1;
+  guint format      : GSK_GL_UNIFORM_FORMAT_BITS;
+  guint array_count : GSK_GL_UNIFORM_ARRAY_BITS;
+  guint offset      : GSK_GL_UNIFORM_OFFSET_BITS;
+} GskGLUniformInfo;
+
+G_STATIC_ASSERT (sizeof (GskGLUniformInfo) == 4);
+
+typedef struct _GskGLUniformMapping
+{
+  const char *name;
+  GskGLUniformInfo info;
+  guint stamp;
+  int location;
+} GskGLUniformMapping;
+
+typedef struct _GskGLUniformProgram
+{
+  guint program_id;
+  guint n_uniforms : 12;
+  guint has_attachments : 1;
+  guint n_mappings;
+  GskGLUniformMapping mappings[32];
+} GskGLUniformProgram;
+
+typedef struct _GskGLUniformState
+{
+  GHashTable *programs;
+  guint8 *values_buf;
+  guint values_pos;
+  guint values_len;
+  GskGLUniformInfo apply_hash[512];
+} GskGLUniformState;
+
+typedef enum _GskGLUniformKind
+{
+  GSK_GL_UNIFORM_FORMAT_1F = 1,
+  GSK_GL_UNIFORM_FORMAT_2F,
+  GSK_GL_UNIFORM_FORMAT_3F,
+  GSK_GL_UNIFORM_FORMAT_4F,
+
+  GSK_GL_UNIFORM_FORMAT_1FV,
+  GSK_GL_UNIFORM_FORMAT_2FV,
+  GSK_GL_UNIFORM_FORMAT_3FV,
+  GSK_GL_UNIFORM_FORMAT_4FV,
+
+  GSK_GL_UNIFORM_FORMAT_1I,
+  GSK_GL_UNIFORM_FORMAT_2I,
+  GSK_GL_UNIFORM_FORMAT_3I,
+  GSK_GL_UNIFORM_FORMAT_4I,
+
+  GSK_GL_UNIFORM_FORMAT_1UI,
+
+  GSK_GL_UNIFORM_FORMAT_TEXTURE,
+
+  GSK_GL_UNIFORM_FORMAT_MATRIX,
+  GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT,
+  GSK_GL_UNIFORM_FORMAT_COLOR,
+
+  GSK_GL_UNIFORM_FORMAT_LAST
+} GskGLUniformFormat;
+
+G_STATIC_ASSERT (GSK_GL_UNIFORM_FORMAT_LAST < (1 << GSK_GL_UNIFORM_FORMAT_BITS));
+
+GskGLUniformState   *gsk_gl_uniform_state_new         (void);
+GskGLUniformState   *gsk_gl_uniform_state_ref         (GskGLUniformState          *state);
+void                 gsk_gl_uniform_state_unref       (GskGLUniformState          *state);
+GskGLUniformProgram *gsk_gl_uniform_state_get_program (GskGLUniformState          *state,
+                                                       guint                       program,
+                                                       const GskGLUniformMapping  *mappings,
+                                                       guint                       n_mappings);
+void                 gsk_gl_uniform_state_end_frame   (GskGLUniformState          *state);
+gsize                gsk_gl_uniform_format_size       (GskGLUniformFormat          format);
+gpointer             gsk_gl_uniform_state_init_value  (GskGLUniformState          *state,
+                                                       GskGLUniformProgram        *program,
+                                                       GskGLUniformFormat          format,
+                                                       guint                       array_count,
+                                                       guint                       key,
+                                                       GskGLUniformMapping       **out_mapping);
+
+#define GSK_GL_UNIFORM_VALUE(base, offset) ((gpointer)((base) + ((offset) * 4)))
+#define gsk_gl_uniform_state_get_uniform_data(state,offset) GSK_GL_UNIFORM_VALUE((state)->values_buf, offset)
+
+static inline gpointer
+gsk_gl_uniform_state_get_value (GskGLUniformState    *state,
+                                GskGLUniformProgram  *program,
+                                GskGLUniformFormat    format,
+                                guint                 array_count,
+                                guint                 key,
+                                guint                 stamp,
+                                GskGLUniformMapping **out_mapping)
+{
+  GskGLUniformMapping *mapping;
+
+  g_assert (key < G_N_ELEMENTS (program->mappings));
+  g_assert (key < program->n_mappings);
+
+  mapping = &program->mappings[key];
+
+  /* Short-circuit if the program optimized the uniform out */
+  if (mapping->location == -1)
+    return NULL;
+
+  /* If the stamp is the same, then we can ignore the request
+   * and short-circuit as early as possible. This requires the
+   * caller to increment their private stamp when they change
+   * internal state.
+   *
+   * This is generally used for the shared uniforms like projection,
+   * modelview, clip, etc to avoid so many comparisons which cost
+   * considerable CPU.
+   */
+  if (stamp != 0 && stamp == mapping->stamp)
+    return NULL;
+
+  if G_LIKELY (format == mapping->info.format && array_count <= mapping->info.array_count)
+    {
+      *out_mapping = mapping;
+      return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
+    }
+
+  return gsk_gl_uniform_state_init_value (state, program, format, array_count, key, out_mapping);
+}
+
+G_GNUC_PURE static inline guint
+gsk_gl_uniform_state_align (guint current_pos,
+                            guint size)
+{
+  guint align = size > 8 ? 16 : (size > 4 ? 8 : 4);
+  guint masked = current_pos & (align - 1);
+
+  g_assert (size > 0);
+  g_assert (align == 4 || align == 8 || align == 16);
+  g_assert (masked < align);
+
+  return align - masked;
+}
+
+static inline gpointer
+gsk_gl_uniform_state_realloc (GskGLUniformState *state,
+                              guint              size,
+                              guint             *offset)
+{
+  guint padding = gsk_gl_uniform_state_align (state->values_pos, size);
+
+  if G_UNLIKELY (state->values_len - padding - size < state->values_pos)
+    {
+      state->values_len *= 2;
+      state->values_buf = g_realloc (state->values_buf, state->values_len);
+    }
+
+  /* offsets are in slots of 4 to use fewer bits */
+  g_assert ((state->values_pos + padding) % 4 == 0);
+  *offset = (state->values_pos + padding) / 4;
+  state->values_pos += padding + size;
+
+  return GSK_GL_UNIFORM_VALUE (state->values_buf, *offset);
+}
+
+#define GSK_GL_UNIFORM_STATE_REPLACE(info, u, type, count)                                \
+  G_STMT_START {                                                                           \
+    if ((info)->info.initial && count == (info)->info.array_count)                         \
+      {                                                                                    \
+        u = GSK_GL_UNIFORM_VALUE (state->values_buf, (info)->info.offset);                \
+      }                                                                                    \
+    else                                                                                   \
+      {                                                                                    \
+        guint offset;                                                                      \
+        u = gsk_gl_uniform_state_realloc (state, sizeof(type) * MAX (1, count), &offset); \
+        g_assert (offset < (1 << GSK_GL_UNIFORM_OFFSET_BITS));                            \
+        (info)->info.offset = offset;                                                      \
+        /* We might have increased array length */                                         \
+        (info)->info.array_count = count;                                                  \
+      }                                                                                    \
+  } G_STMT_END
+
+static inline void
+gsk_gl_uniform_info_changed (GskGLUniformMapping *info,
+                             guint                stamp)
+{
+  info->stamp = stamp;
+  info->info.initial = FALSE;
+}
+
+static inline void
+gsk_gl_uniform_state_set1f (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            float                value0)
+{
+  Uniform1f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != 0);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1F, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1f , 1);
+          u->v0 = value0;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set2f (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            float                value0,
+                            float                value1)
+{
+  Uniform2f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2F, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set3f (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            float                value0,
+                            float                value1,
+                            float                value2)
+{
+  Uniform3f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3F, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set4f (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            float                value0,
+                            float                value1,
+                            float                value2,
+                            float                value3)
+{
+  Uniform4f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4F, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          u->v3 = value3;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set1ui (GskGLUniformState   *state,
+                             GskGLUniformProgram *program,
+                             guint                key,
+                             guint                stamp,
+                             guint                value0)
+{
+  Uniform1ui *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1UI, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1ui, 1);
+          u->v0 = value0;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set1i (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            int                  value0)
+{
+  Uniform1i *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1I, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1i, 1);
+          u->v0 = value0;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set2i (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            int                  value0,
+                            int                  value1)
+{
+  Uniform2i *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2I, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2i, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set3i (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            int                  value0,
+                            int                  value1,
+                            int                  value2)
+{
+  Uniform3i *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3I, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3i, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set4i (GskGLUniformState   *state,
+                            GskGLUniformProgram *program,
+                            guint                key,
+                            guint                stamp,
+                            int                  value0,
+                            int                  value1,
+                            int                  value2,
+                            int                  value3)
+{
+  Uniform4i *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4I, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4i, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          u->v3 = value3;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set_rounded_rect (GskGLUniformState    *state,
+                                       GskGLUniformProgram  *program,
+                                       guint                 key,
+                                       guint                 stamp,
+                                       const GskRoundedRect *rounded_rect)
+{
+  GskRoundedRect *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (rounded_rect != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || memcmp (u, rounded_rect, sizeof *u) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, GskRoundedRect, 1);
+          memcpy (u, rounded_rect, sizeof *rounded_rect);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set_matrix (GskGLUniformState       *state,
+                                 GskGLUniformProgram     *program,
+                                 guint                    key,
+                                 guint                    stamp,
+                                 const graphene_matrix_t *matrix)
+{
+  graphene_matrix_t *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (matrix != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_MATRIX, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || memcmp (u, matrix, sizeof *u) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, graphene_matrix_t, 1);
+          memcpy (u, matrix, sizeof *matrix);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+/**
+ * gsk_gl_uniform_state_set_texture:
+ * @state: a `GskGLUniformState`
+ * @program: the program id
+ * @location: the location of the texture
+ * @texture_slot: a texturing slot such as GL_TEXTURE0
+ *
+ * Sets the uniform expecting a texture to @texture_slot. This API
+ * expects a texture slot such as GL_TEXTURE0 to reduce chances of
+ * miss-use by the caller.
+ *
+ * The value stored to the uniform is in the form of 0 for GL_TEXTURE0,
+ * 1 for GL_TEXTURE1, and so on.
+ */
+static inline void
+gsk_gl_uniform_state_set_texture (GskGLUniformState   *state,
+                                  GskGLUniformProgram *program,
+                                  guint                key,
+                                  guint                stamp,
+                                  guint                texture_slot)
+{
+  GskGLUniformMapping *info;
+  guint *u;
+
+  g_assert (texture_slot >= GL_TEXTURE0);
+  g_assert (texture_slot < GL_TEXTURE16);
+
+  texture_slot -= GL_TEXTURE0;
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_TEXTURE, 1, key, stamp, &info)))
+    {
+      if (info->info.initial || *u != texture_slot)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, guint, 1);
+          *u = texture_slot;
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+/**
+ * gsk_gl_uniform_state_set_color:
+ * @state: a `GskGLUniformState`
+ * @program: a program id > 0
+ * @location: the uniform location
+ * @color: a color to set or %NULL for transparent
+ *
+ * Sets a uniform to the color described by @color. This is a convenience
+ * function to allow callers to avoid having to translate colors to floats
+ * in other portions of the renderer.
+ */
+static inline void
+gsk_gl_uniform_state_set_color (GskGLUniformState   *state,
+                                GskGLUniformProgram *program,
+                                guint                key,
+                                guint                stamp,
+                                const GdkRGBA       *color)
+{
+  static const GdkRGBA transparent = {0};
+  GskGLUniformMapping *info;
+  GdkRGBA *u;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_COLOR, 1, key, stamp, &info)))
+    {
+      if (color == NULL)
+        color = &transparent;
+
+      if (info->info.initial || memcmp (color, u, sizeof *u) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, GdkRGBA, 1);
+          memcpy (u, color, sizeof *color);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set1fv (GskGLUniformState   *state,
+                             GskGLUniformProgram *program,
+                             guint                key,
+                             guint                stamp,
+                             guint                count,
+                             const float         *value)
+{
+  Uniform1f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (count > 0);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1FV, count, key, stamp, &info)))
+    {
+      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1f, count);
+          memcpy (u, value, sizeof (Uniform1f) * count);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set2fv (GskGLUniformState   *state,
+                             GskGLUniformProgram *program,
+                             guint                key,
+                             guint                stamp,
+                             guint                count,
+                             const float         *value)
+{
+  Uniform2f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (count > 0);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2FV, count, key, stamp, &info)))
+    {
+      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, count);
+          memcpy (u, value, sizeof (Uniform2f) * count);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set3fv (GskGLUniformState   *state,
+                             GskGLUniformProgram *program,
+                             guint                key,
+                             guint                stamp,
+                             guint                count,
+                             const float         *value)
+{
+  Uniform3f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (count > 0);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3FV, count, key, stamp, &info)))
+    {
+      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, count);
+          memcpy (u, value, sizeof (Uniform3f) * count);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline void
+gsk_gl_uniform_state_set4fv (GskGLUniformState   *state,
+                             GskGLUniformProgram *program,
+                             guint                key,
+                             guint                stamp,
+                             guint                count,
+                             const float         *value)
+{
+  Uniform4f *u;
+  GskGLUniformMapping *info;
+
+  g_assert (state != NULL);
+  g_assert (program != NULL);
+  g_assert (count > 0);
+
+  if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4FV, count, key, stamp, &info)))
+    {
+      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
+        {
+          GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, count);
+          memcpy (u, value, sizeof (Uniform4f) * count);
+          gsk_gl_uniform_info_changed (info, stamp);
+        }
+    }
+}
+
+static inline guint
+gsk_gl_uniform_state_fmix (guint program,
+                           guint location)
+{
+  guint h = (program << 16) | location;
+
+   h ^= h >> 16;
+   h *= 0x85ebca6b;
+   h ^= h >> 13;
+   h *= 0xc2b2ae35;
+   h ^= h >> 16;
+
+   return h;
+}
+
+/*
+ * gsk_gl_uniform_state_apply:
+ * @state: the uniform state
+ * @program: the program id
+ * @location: the location of the uniform
+ * @offset: the offset of the data within the buffer
+ * @info: the uniform info
+ *
+ * This function can be used to apply state that was previously recorded
+ * by the `GskGLUniformState`.
+ *
+ * It is specifically useful from the `GskGLCommandQueue` to execute uniform
+ * changes but only when they have changed from the current value.
+ */
+static inline void
+gsk_gl_uniform_state_apply (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            GskGLUniformInfo   info)
+{
+  guint index = gsk_gl_uniform_state_fmix (program, location) % G_N_ELEMENTS (state->apply_hash);
+  gconstpointer dataptr = GSK_GL_UNIFORM_VALUE (state->values_buf, info.offset);
+
+  /* aligned, can treat as unsigned */
+  if (*(guint *)&info == *(guint *)&state->apply_hash[index])
+    return;
+
+  state->apply_hash[index] = info;
+
+  /* TODO: We could do additional comparisons here to make sure we are
+   *       changing state.
+   */
+
+  switch (info.format)
+    {
+    case GSK_GL_UNIFORM_FORMAT_1F:
+      glUniform1fv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_2F:
+      glUniform2fv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_3F:
+      glUniform3fv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_4F:
+      glUniform4fv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_1FV:
+      glUniform1fv (location, info.array_count, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_2FV:
+      glUniform2fv (location, info.array_count, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_3FV:
+      glUniform3fv (location, info.array_count, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_4FV:
+      glUniform4fv (location, info.array_count, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_1I:
+    case GSK_GL_UNIFORM_FORMAT_TEXTURE:
+      glUniform1iv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_2I:
+      glUniform2iv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_3I:
+      glUniform3iv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_4I:
+      glUniform4iv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_1UI:
+      glUniform1uiv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_MATRIX: {
+      float mat[16];
+      graphene_matrix_to_float (dataptr, mat);
+      glUniformMatrix4fv (location, 1, GL_FALSE, mat);
+#if 0
+      /* TODO: If Graphene can give us a peek here on platforms
+       * where the format is float[16] (most/all x86_64?) then
+       * We can avoid the SIMD operation to convert the format.
+       */
+      G_STATIC_ASSERT (sizeof (graphene_matrix_t) == 16*4);
+      glUniformMatrix4fv (location, 1, GL_FALSE, dataptr);
+#endif
+    }
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_COLOR:
+      glUniform4fv (location, 1, dataptr);
+    break;
+
+    case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT:
+      glUniform4fv (location, 3, dataptr);
+    break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+G_END_DECLS
+
+#endif /* GSK_GL_UNIFORM_STATE_PRIVATE_H */
diff --git a/gsk/gl/inlinearray.h b/gsk/gl/inlinearray.h
new file mode 100644 (file)
index 0000000..98a9255
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef __INLINE_ARRAY_H__
+#define __INLINE_ARRAY_H__
+
+#define DEFINE_INLINE_ARRAY(Type, prefix, ElementType)              \
+  typedef struct _##Type {                                          \
+    gsize len;                                                      \
+    gsize allocated;                                                \
+    ElementType *items;                                             \
+  } Type;                                                           \
+                                                                    \
+  static inline void                                                \
+  prefix##_init (Type  *ar,                                         \
+                 gsize  initial_size)                               \
+  {                                                                 \
+    ar->len = 0;                                                    \
+    ar->allocated = initial_size ? initial_size : 16;               \
+    ar->items = g_new0 (ElementType, ar->allocated);                \
+  }                                                                 \
+                                                                    \
+  static inline void                                                \
+  prefix##_clear (Type *ar)                                         \
+  {                                                                 \
+    ar->len = 0;                                                    \
+    ar->allocated = 0;                                              \
+    g_clear_pointer (&ar->items, g_free);                           \
+  }                                                                 \
+                                                                    \
+  static inline ElementType *                                       \
+  prefix##_head (Type *ar)                                          \
+  {                                                                 \
+    return &ar->items[0];                                           \
+  }                                                                 \
+                                                                    \
+  static inline ElementType *                                       \
+  prefix##_tail (Type *ar)                                          \
+  {                                                                 \
+    return &ar->items[ar->len-1];                                   \
+  }                                                                 \
+                                                                    \
+  static inline ElementType *                                       \
+  prefix##_append (Type *ar)                                        \
+  {                                                                 \
+    if G_UNLIKELY (ar->len == ar->allocated)                        \
+      {                                                             \
+        ar->allocated *= 2;                                         \
+        ar->items = g_renew (ElementType, ar->items, ar->allocated);\
+      }                                                             \
+                                                                    \
+    ar->len++;                                                      \
+                                                                    \
+    return prefix##_tail (ar);                                      \
+  }                                                                 \
+                                                                    \
+  static inline ElementType *                                       \
+  prefix##_append_n (Type  *ar,                                     \
+                     gsize  n)                                      \
+  {                                                                 \
+    if G_UNLIKELY ((ar->len + n) > ar->allocated)                   \
+      {                                                             \
+        while ((ar->len + n) > ar->allocated)                       \
+          ar->allocated *= 2;                                       \
+        ar->items = g_renew (ElementType, ar->items, ar->allocated);\
+      }                                                             \
+                                                                    \
+    ar->len += n;                                                   \
+                                                                    \
+    return &ar->items[ar->len-n];                                   \
+  }                                                                 \
+                                                                    \
+  static inline gsize                                               \
+  prefix##_index_of (Type              *ar,                         \
+                     const ElementType *element)                    \
+  {                                                                 \
+    return element - &ar->items[0];                                 \
+  }
+
+#endif /* __INLINE_ARRAY_H__ */
diff --git a/gsk/gl/ninesliceprivate.h b/gsk/gl/ninesliceprivate.h
new file mode 100644 (file)
index 0000000..1ad9059
--- /dev/null
@@ -0,0 +1,310 @@
+/* ninesliceprivate.h
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2021 Christian Hergert <chergert@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __NINE_SLICE_PRIVATE_H__
+#define __NINE_SLICE_PRIVATE_H__
+
+#include "gskgltextureprivate.h"
+
+#if 0
+# define DEBUG_NINE_SLICE
+#endif
+
+G_BEGIN_DECLS
+
+enum {
+  NINE_SLICE_TOP_LEFT      = 0,
+  NINE_SLICE_TOP_CENTER    = 1,
+  NINE_SLICE_TOP_RIGHT     = 2,
+  NINE_SLICE_LEFT_CENTER   = 3,
+  NINE_SLICE_CENTER        = 4,
+  NINE_SLICE_RIGHT_CENTER  = 5,
+  NINE_SLICE_BOTTOM_LEFT   = 6,
+  NINE_SLICE_BOTTOM_CENTER = 7,
+  NINE_SLICE_BOTTOM_RIGHT  = 8,
+};
+
+static inline bool G_GNUC_PURE
+nine_slice_is_visible (const GskGLTextureNineSlice *slice)
+{
+  return slice->rect.width > 0 && slice->rect.height > 0;
+}
+
+static inline void
+nine_slice_rounded_rect (GskGLTextureNineSlice *slices,
+                         const GskRoundedRect  *rect)
+{
+  const graphene_point_t *origin = &rect->bounds.origin;
+  const graphene_size_t *size = &rect->bounds.size;
+  int top_height = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].height,
+                               rect->corner[GSK_CORNER_TOP_RIGHT].height));
+  int bottom_height = ceilf (MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
+                                  rect->corner[GSK_CORNER_BOTTOM_RIGHT].height));
+  int right_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width,
+                                rect->corner[GSK_CORNER_BOTTOM_RIGHT].width));
+  int left_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].width,
+                               rect->corner[GSK_CORNER_BOTTOM_LEFT].width));
+
+  /* Top left */
+  slices[0].rect.x = origin->x;
+  slices[0].rect.y = origin->y;
+  slices[0].rect.width = left_width;
+  slices[0].rect.height = top_height;
+
+  /* Top center */
+  slices[1].rect.x = origin->x + size->width / 2.0 - 0.5;
+  slices[1].rect.y = origin->y;
+  slices[1].rect.width = 1;
+  slices[1].rect.height = top_height;
+
+  /* Top right */
+  slices[2].rect.x = origin->x + size->width - right_width;
+  slices[2].rect.y = origin->y;
+  slices[2].rect.width = right_width;
+  slices[2].rect.height = top_height;
+
+  /* Left center */
+  slices[3].rect.x = origin->x;
+  slices[3].rect.y = origin->y + size->height / 2;
+  slices[3].rect.width = left_width;
+  slices[3].rect.height = 1;
+
+  /* center */
+  slices[4].rect.x = origin->x + size->width / 2.0 - 0.5;
+  slices[4].rect.y = origin->y + size->height / 2.0 - 0.5;
+  slices[4].rect.width = 1;
+  slices[4].rect.height = 1;
+
+  /* Right center */
+  slices[5].rect.x = origin->x + size->width - right_width;
+  slices[5].rect.y = origin->y + (size->height / 2.0) - 0.5;
+  slices[5].rect.width = right_width;
+  slices[5].rect.height = 1;
+
+  /* Bottom Left */
+  slices[6].rect.x = origin->x;
+  slices[6].rect.y = origin->y + size->height - bottom_height;
+  slices[6].rect.width = left_width;
+  slices[6].rect.height = bottom_height;
+
+  /* Bottom center */
+  slices[7].rect.x = origin->x + (size->width / 2.0) - 0.5;
+  slices[7].rect.y = origin->y + size->height - bottom_height;
+  slices[7].rect.width = 1;
+  slices[7].rect.height = bottom_height;
+
+  /* Bottom right */
+  slices[8].rect.x = origin->x + size->width - right_width;
+  slices[8].rect.y = origin->y + size->height - bottom_height;
+  slices[8].rect.width = right_width;
+  slices[8].rect.height = bottom_height;
+
+#ifdef DEBUG_NINE_SLICE
+  /* These only hold true when the values from ceilf() above
+   * are greater than one. Otherwise they fail, like will happen
+   * with the node editor viewing the textures zoomed out.
+   */
+  if (size->width > 1)
+    g_assert_cmpfloat (size->width, >=, left_width + right_width);
+  if (size->height > 1)
+  g_assert_cmpfloat (size->height, >=, top_height + bottom_height);
+#endif
+}
+
+static inline void
+nine_slice_to_texture_coords (GskGLTextureNineSlice *slices,
+                              int                    texture_width,
+                              int                    texture_height)
+{
+  float fw = texture_width;
+  float fh = texture_height;
+
+  for (guint i = 0; i < 9; i++)
+    {
+      GskGLTextureNineSlice *slice = &slices[i];
+
+      slice->area.x = slice->rect.x / fw;
+      slice->area.y = 1.0 - ((slice->rect.y + slice->rect.height) / fh);
+      slice->area.x2 = ((slice->rect.x + slice->rect.width) / fw);
+      slice->area.y2 = (1.0 - (slice->rect.y / fh));
+
+#ifdef DEBUG_NINE_SLICE
+      g_assert_cmpfloat (slice->area.x, >=, 0);
+      g_assert_cmpfloat (slice->area.x, <=, 1);
+      g_assert_cmpfloat (slice->area.y, >=, 0);
+      g_assert_cmpfloat (slice->area.y, <=, 1);
+      g_assert_cmpfloat (slice->area.x2, >, slice->area.x);
+      g_assert_cmpfloat (slice->area.y2, >, slice->area.y);
+#endif
+    }
+}
+
+static inline void
+nine_slice_grow (GskGLTextureNineSlice *slices,
+                 int                    amount_x,
+                 int                    amount_y)
+{
+  if (amount_x == 0 && amount_y == 0)
+    return;
+
+  /* top left */
+  slices[0].rect.x -= amount_x;
+  slices[0].rect.y -= amount_y;
+  if (amount_x > slices[0].rect.width)
+    slices[0].rect.width += amount_x * 2;
+  else
+    slices[0].rect.width += amount_x;
+
+  if (amount_y > slices[0].rect.height)
+    slices[0].rect.height += amount_y * 2;
+  else
+    slices[0].rect.height += amount_y;
+
+
+  /* Top center */
+  slices[1].rect.y -= amount_y;
+  if (amount_y > slices[1].rect.height)
+    slices[1].rect.height += amount_y * 2;
+  else
+    slices[1].rect.height += amount_y;
+
+  /* top right */
+  slices[2].rect.y -= amount_y;
+  if (amount_x > slices[2].rect.width)
+    {
+      slices[2].rect.x -= amount_x;
+      slices[2].rect.width += amount_x * 2;
+    }
+  else
+    {
+     slices[2].rect.width += amount_x;
+    }
+
+  if (amount_y > slices[2].rect.height)
+    slices[2].rect.height += amount_y * 2;
+  else
+    slices[2].rect.height += amount_y;
+
+
+
+  slices[3].rect.x -= amount_x;
+  if (amount_x > slices[3].rect.width)
+    slices[3].rect.width += amount_x * 2;
+  else
+    slices[3].rect.width += amount_x;
+
+  /* Leave center alone */
+
+  if (amount_x > slices[5].rect.width)
+    {
+      slices[5].rect.x -= amount_x;
+      slices[5].rect.width += amount_x * 2;
+    }
+  else
+    {
+      slices[5].rect.width += amount_x;
+    }
+
+
+  /* Bottom left */
+  slices[6].rect.x -= amount_x;
+  if (amount_x > slices[6].rect.width)
+    {
+      slices[6].rect.width += amount_x * 2;
+    }
+  else
+    {
+      slices[6].rect.width += amount_x;
+    }
+
+  if (amount_y > slices[6].rect.height)
+    {
+      slices[6].rect.y -= amount_y;
+      slices[6].rect.height += amount_y * 2;
+    }
+  else
+    {
+      slices[6].rect.height += amount_y;
+    }
+
+
+  /* Bottom center */
+  if (amount_y > slices[7].rect.height)
+    {
+      slices[7].rect.y -= amount_y;
+      slices[7].rect.height += amount_y * 2;
+    }
+  else
+    {
+      slices[7].rect.height += amount_y;
+    }
+
+  if (amount_x > slices[8].rect.width)
+    {
+      slices[8].rect.x -= amount_x;
+      slices[8].rect.width += amount_x * 2;
+    }
+  else
+    {
+      slices[8].rect.width += amount_x;
+    }
+
+  if (amount_y > slices[8].rect.height)
+    {
+      slices[8].rect.y -= amount_y;
+      slices[8].rect.height += amount_y * 2;
+    }
+  else
+    {
+      slices[8].rect.height += amount_y;
+    }
+
+#ifdef DEBUG_NINE_SLICE
+  /* These cannot be relied on in all cases right now, specifically
+   * when viewing data zoomed out.
+   */
+  for (guint i = 0; i < 9; i ++)
+    {
+      g_assert_cmpint (slices[i].rect.x, >=, 0);
+      g_assert_cmpint (slices[i].rect.y, >=, 0);
+      g_assert_cmpint (slices[i].rect.width, >=, 0);
+      g_assert_cmpint (slices[i].rect.height, >=, 0);
+    }
+
+  /* Rows don't overlap */
+  for (guint i = 0; i < 3; i++)
+    {
+      int lhs = slices[i * 3 + 0].rect.x + slices[i * 3 + 0].rect.width;
+      int rhs = slices[i * 3 + 1].rect.x;
+
+      /* Ignore the case where we are scaled out and the
+       * positioning is degenerate, such as from node-editor.
+       */
+      if (rhs > 1)
+        g_assert_cmpint (lhs, <, rhs);
+    }
+#endif
+
+}
+
+G_END_DECLS
+
+#endif /* __NINE_SLICE_PRIVATE_H__ */
diff --git a/gsk/gl/resources/blend.glsl b/gsk/gl/resources/blend.glsl
new file mode 100644 (file)
index 0000000..609f9f2
--- /dev/null
@@ -0,0 +1,314 @@
+// VERTEX_SHADER:
+// blend.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// blend.glsl
+
+uniform int u_mode;
+uniform sampler2D u_source2;
+
+float
+combine (float source, float backdrop)
+{
+  return source + backdrop * (1.0 - source);
+}
+
+vec4
+composite (vec4 Cs, vec4 Cb, vec3 B)
+{
+  float ao = Cs.a + Cb.a * (1.0 - Cs.a);
+  vec3 Co = (Cs.a*(1.0 - Cb.a)*Cs.rgb + Cs.a*Cb.a*B + (1.0 - Cs.a)*Cb.a*Cb.rgb) / ao;
+  return vec4(Co, ao);
+}
+
+vec4
+normal (vec4 Cs, vec4 Cb)
+{
+  return composite (Cs, Cb, Cs.rgb);
+}
+
+vec4
+multiply (vec4 Cs, vec4 Cb)
+{
+  return composite (Cs, Cb, Cs.rgb * Cb.rgb);
+}
+
+vec4
+difference (vec4 Cs, vec4 Cb)
+{
+  return composite (Cs, Cb, abs(Cs.rgb - Cb.rgb));
+}
+
+vec4
+screen (vec4 Cs, vec4 Cb)
+{
+  return composite (Cs, Cb, Cs.rgb + Cb.rgb - Cs.rgb * Cb.rgb);
+}
+
+float
+hard_light (float source, float backdrop)
+{
+  if (source <= 0.5)
+    return 2.0 * backdrop * source;
+  else
+    return 2.0 * (backdrop + source - backdrop * source) - 1.0;
+}
+
+vec4
+hard_light (vec4 Cs, vec4 Cb)
+{
+  vec3 B = vec3 (hard_light (Cs.r, Cb.r),
+                 hard_light (Cs.g, Cb.g),
+                 hard_light (Cs.b, Cb.b));
+  return composite (Cs, Cb, B);
+}
+
+float
+soft_light (float source, float backdrop)
+{
+  float db;
+
+  if (backdrop <= 0.25)
+    db = ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop;
+  else
+    db = sqrt (backdrop);
+
+  if (source <= 0.5)
+    return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop);
+  else
+    return backdrop + (2.0 * source - 1.0) * (db - backdrop);
+}
+
+vec4
+soft_light (vec4 Cs, vec4 Cb)
+{
+  vec3 B = vec3 (soft_light (Cs.r, Cb.r),
+                 soft_light (Cs.g, Cb.g),
+                 soft_light (Cs.b, Cb.b));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+overlay (vec4 Cs, vec4 Cb)
+{
+  vec3 B = vec3 (hard_light (Cb.r, Cs.r),
+                 hard_light (Cb.g, Cs.g),
+                 hard_light (Cb.b, Cs.b));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+darken (vec4 Cs, vec4 Cb)
+{
+  vec3 B = min (Cs.rgb, Cb.rgb);
+  return composite (Cs, Cb, B);
+}
+
+vec4
+lighten (vec4 Cs, vec4 Cb)
+{
+  vec3 B = max (Cs.rgb, Cb.rgb);
+  return composite (Cs, Cb, B);
+}
+
+float
+color_dodge (float source, float backdrop)
+{
+  return (source == 1.0) ? source : min (backdrop / (1.0 - source), 1.0);
+}
+
+vec4
+color_dodge (vec4 Cs, vec4 Cb)
+{
+  vec3 B = vec3 (color_dodge (Cs.r, Cb.r),
+                 color_dodge (Cs.g, Cb.g),
+                 color_dodge (Cs.b, Cb.b));
+  return composite (Cs, Cb, B);
+}
+
+
+float
+color_burn (float source, float backdrop)
+{
+  return (source == 0.0) ? source : max ((1.0 - ((1.0 - backdrop) / source)), 0.0);
+}
+
+vec4
+color_burn (vec4 Cs, vec4 Cb)
+{
+  vec3 B = vec3 (color_burn (Cs.r, Cb.r),
+                 color_burn (Cs.g, Cb.g),
+                 color_burn (Cs.b, Cb.b));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+exclusion (vec4 Cs, vec4 Cb)
+{
+  vec3 B = Cb.rgb + Cs.rgb - 2.0 * Cb.rgb * Cs.rgb;
+  return composite (Cs, Cb, B);
+}
+
+float
+lum (vec3 c)
+{
+  return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
+}
+
+vec3
+clip_color (vec3 c)
+{
+  float l = lum (c);
+  float n = min (c.r, min (c.g, c.b));
+  float x = max (c.r, max (c.g, c.b));
+  if (n < 0.0) c = l + (((c - l) * l) / (l - n));
+  if (x > 1.0) c = l + (((c - l) * (1.0 - l)) / (x - l));
+  return c;
+}
+
+vec3
+set_lum (vec3 c, float l)
+{
+  float d = l - lum (c);
+  return clip_color (vec3 (c.r + d, c.g + d, c.b + d));
+}
+
+float
+sat (vec3 c)
+{
+  return max (c.r, max (c.g, c.b)) - min (c.r, min (c.g, c.b));
+}
+
+vec3
+set_sat (vec3 c, float s)
+{
+  float cmin = min (c.r, min (c.g, c.b));
+  float cmax = max (c.r, max (c.g, c.b));
+  vec3 res;
+
+  if (cmax == cmin)
+    res = vec3 (0, 0, 0);
+  else
+    {
+      if (c.r == cmax)
+        {
+          if (c.g == cmin)
+            {
+              res.b = ((c.b - cmin) * s) / (cmax - cmin);
+              res.g = 0.0;
+            }
+          else
+            {
+              res.g = ((c.g - cmin) * s) / (cmax - cmin);
+              res.b = 0.0;
+            }
+          res.r = s;
+        }
+      else if (c.g == cmax)
+        {
+          if (c.r == cmin)
+            {
+              res.b = ((c.b - cmin) * s) / (cmax - cmin);
+              res.r = 0.0;
+            }
+          else
+            {
+              res.r = ((c.r - cmin) * s) / (cmax - cmin);
+              res.b = 0.0;
+            }
+          res.g = s;
+        }
+      else
+        {
+          if (c.r == cmin)
+            {
+              res.g = ((c.g - cmin) * s) / (cmax - cmin);
+              res.r = 0.0;
+            }
+          else
+            {
+              res.r = ((c.r - cmin) * s) / (cmax - cmin);
+              res.g = 0.0;
+            }
+          res.b = s;
+        }
+    }
+  return res;
+}
+
+vec4
+color (vec4 Cs, vec4 Cb)
+{
+  vec3 B = set_lum (Cs.rgb, lum (Cb.rgb));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+hue (vec4 Cs, vec4 Cb)
+{
+  vec3 B = set_lum (set_sat (Cs.rgb, sat (Cb.rgb)), lum (Cb.rgb));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+saturation (vec4 Cs, vec4 Cb)
+{
+  vec3 B = set_lum (set_sat (Cb.rgb, sat (Cs.rgb)), lum (Cb.rgb));
+  return composite (Cs, Cb, B);
+}
+
+vec4
+luminosity (vec4 Cs, vec4 Cb)
+{
+  vec3 B = set_lum (Cb.rgb, lum (Cs.rgb));
+  return composite (Cs, Cb, B);
+}
+
+void main() {
+  vec4 bottom_color = GskTexture(u_source, vUv);
+  vec4 top_color = GskTexture(u_source2, vUv);
+
+  vec4 result;
+  if (u_mode == 0)
+    result = normal(top_color, bottom_color);
+  else if (u_mode == 1)
+    result = multiply(top_color, bottom_color);
+  else if (u_mode == 2)
+    result = screen(top_color, bottom_color);
+  else if (u_mode == 3)
+    result = overlay(top_color, bottom_color);
+  else if (u_mode == 4)
+    result = darken(top_color, bottom_color);
+  else if (u_mode == 5)
+    result = lighten(top_color, bottom_color);
+  else if (u_mode == 6)
+    result = color_dodge(top_color, bottom_color);
+  else if (u_mode == 7)
+    result = color_burn(top_color, bottom_color);
+  else if (u_mode == 8)
+    result = hard_light(top_color, bottom_color);
+  else if (u_mode == 9)
+    result = soft_light(top_color, bottom_color);
+  else if (u_mode == 10)
+    result = difference(top_color, bottom_color);
+  else if (u_mode == 11)
+    result = exclusion(top_color, bottom_color);
+  else if (u_mode == 12)
+    result = color(top_color, bottom_color);
+  else if (u_mode == 13)
+    result = hue(top_color, bottom_color);
+  else if (u_mode == 14)
+    result = saturation(top_color, bottom_color);
+  else if (u_mode == 15)
+    result = luminosity(top_color, bottom_color);
+  else
+    discard;
+
+  gskSetScaledOutputColor(result, u_alpha);
+}
diff --git a/gsk/gl/resources/blit.glsl b/gsk/gl/resources/blit.glsl
new file mode 100644 (file)
index 0000000..ced047b
--- /dev/null
@@ -0,0 +1,17 @@
+// VERTEX_SHADER:
+// blit.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// blit.glsl
+
+void main() {
+  vec4 diffuse = GskTexture(u_source, vUv);
+
+  gskSetScaledOutputColor(diffuse, u_alpha);
+}
diff --git a/gsk/gl/resources/blur.glsl b/gsk/gl/resources/blur.glsl
new file mode 100644 (file)
index 0000000..7e7c1c2
--- /dev/null
@@ -0,0 +1,59 @@
+// VERTEX_SHADER:
+// blur.glsl
+
+uniform float u_blur_radius;
+uniform vec2 u_blur_size;
+uniform vec2 u_blur_dir;
+
+_OUT_ vec2 pixel_step;
+_OUT_ float pixels_per_side;
+_OUT_ vec3 initial_gaussian;
+
+const float PI = 3.14159265;
+const float RADIUS_MULTIPLIER = 2.0;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+
+  pixel_step = (vec2(1.0) / u_blur_size) * u_blur_dir;
+  pixels_per_side = floor(u_blur_radius * RADIUS_MULTIPLIER / 2.0);
+
+  float sigma = u_blur_radius / 2.0; // *shrug*
+  initial_gaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
+  initial_gaussian.y = exp(-0.5 / (sigma * sigma));
+  initial_gaussian.z = initial_gaussian.y * initial_gaussian.y;
+}
+
+// FRAGMENT_SHADER:
+// blur.glsl
+
+_IN_ vec2 pixel_step;
+_IN_ float pixels_per_side;
+_IN_ vec3 initial_gaussian;
+
+// blur_radius 0 is NOT supported and MUST be caught before.
+
+// Partially from http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
+void main() {
+  vec3 incrementalGaussian = initial_gaussian;
+
+  float coefficientSum = 0.0;
+  vec4 sum = GskTexture(u_source, vUv) * incrementalGaussian.x;
+  coefficientSum += incrementalGaussian.x;
+  incrementalGaussian.xy *= incrementalGaussian.yz;
+
+  vec2 p = pixel_step;
+  for (int i = 1; i <= int(pixels_per_side); i++) {
+    sum += GskTexture(u_source, vUv - p) * incrementalGaussian.x;
+    sum += GskTexture(u_source, vUv + p) * incrementalGaussian.x;
+
+    coefficientSum += 2.0 * incrementalGaussian.x;
+    incrementalGaussian.xy *= incrementalGaussian.yz;
+
+    p += pixel_step;
+  }
+
+  gskSetOutputColor(sum / coefficientSum);
+}
diff --git a/gsk/gl/resources/border.glsl b/gsk/gl/resources/border.glsl
new file mode 100644 (file)
index 0000000..b8653ba
--- /dev/null
@@ -0,0 +1,43 @@
+// VERTEX_SHADER:
+// border.glsl
+
+uniform vec4 u_widths;
+uniform vec4[3] u_outline_rect;
+
+_OUT_ vec4 final_color;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  final_color = gsk_scaled_premultiply(aColor, u_alpha);
+
+  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
+  GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths);
+
+  gsk_rounded_rect_transform(outside, u_modelview);
+  gsk_rounded_rect_transform(inside, u_modelview);
+
+  gsk_rounded_rect_encode(outside, transformed_outside_outline);
+  gsk_rounded_rect_encode(inside, transformed_inside_outline);
+}
+
+// FRAGMENT_SHADER:
+// border.glsl
+
+uniform vec4[3] u_outline_rect;
+
+_IN_ vec4 final_color;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  vec2 frag = gsk_get_frag_coord();
+
+  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
+                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
+                      0.0, 1.0);
+
+  gskSetScaledOutputColor(final_color, alpha);
+}
diff --git a/gsk/gl/resources/color.glsl b/gsk/gl/resources/color.glsl
new file mode 100644 (file)
index 0000000..ba98b1b
--- /dev/null
@@ -0,0 +1,20 @@
+// VERTEX_SHADER:
+// color.glsl
+
+_OUT_ vec4 final_color;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  final_color = gsk_scaled_premultiply(aColor, u_alpha);
+}
+
+// FRAGMENT_SHADER:
+// color.glsl
+
+_IN_ vec4 final_color;
+
+void main() {
+  gskSetOutputColor(final_color);
+}
+
diff --git a/gsk/gl/resources/color_matrix.glsl b/gsk/gl/resources/color_matrix.glsl
new file mode 100644 (file)
index 0000000..51835f0
--- /dev/null
@@ -0,0 +1,27 @@
+// VERTEX_SHADER:
+// color_matrix.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// color_matrix.glsl
+
+uniform mat4 u_color_matrix;
+uniform vec4 u_color_offset;
+
+void main() {
+  vec4 color = GskTexture(u_source, vUv);
+
+  // Un-premultilpy
+  if (color.a != 0.0)
+    color.rgb /= color.a;
+
+  color = u_color_matrix * color + u_color_offset;
+  color = clamp(color, 0.0, 1.0);
+
+  gskSetOutputColor(gsk_scaled_premultiply(color, u_alpha));
+}
diff --git a/gsk/gl/resources/coloring.glsl b/gsk/gl/resources/coloring.glsl
new file mode 100644 (file)
index 0000000..007c54d
--- /dev/null
@@ -0,0 +1,33 @@
+// VERTEX_SHADER:
+// coloring.glsl
+
+_OUT_ vec4 final_color;
+_OUT_ float use_color;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+
+  // We use this shader for both plain glyphs (used as mask)
+  // and color glpyhs (used as source). The renderer sets
+  // aColor to vec4(-1) for color glyhs.
+  if (distance(aColor,vec4(-1)) < 0.1)
+    use_color = 0.0;
+  else
+    use_color = 1.0;
+
+  final_color = gsk_scaled_premultiply(aColor, u_alpha);
+}
+
+// FRAGMENT_SHADER:
+// coloring.glsl
+
+_IN_ vec4 final_color;
+_IN_ float use_color;
+
+void main() {
+  vec4 diffuse = GskTexture(u_source, vUv);
+
+  gskSetOutputColor(mix(diffuse * u_alpha, final_color * diffuse.a, use_color));
+}
diff --git a/gsk/gl/resources/conic_gradient.glsl b/gsk/gl/resources/conic_gradient.glsl
new file mode 100644 (file)
index 0000000..3df33bc
--- /dev/null
@@ -0,0 +1,85 @@
+// VERTEX_SHADER
+// conic_gradient.glsl
+
+uniform vec4 u_geometry;
+
+_NOPERSPECTIVE_ _OUT_ vec2 coord;
+
+void main() {
+  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
+
+  vec2 mv0 = u_modelview[0].xy;
+  vec2 mv1 = u_modelview[1].xy;
+  vec2 offset = aPosition - u_geometry.xy;
+
+  coord = vec2(dot(mv0, offset), dot(mv1, offset));
+}
+
+// FRAGMENT_SHADER:
+// conic_gradient.glsl
+
+#define MAX_COLOR_STOPS 6
+
+#ifdef GSK_LEGACY
+uniform int u_num_color_stops;
+#else
+uniform highp int u_num_color_stops; // Why? Because it works like this.
+#endif
+
+uniform vec4 u_geometry;
+uniform float u_color_stops[MAX_COLOR_STOPS * 5];
+
+_NOPERSPECTIVE_ _IN_ vec2 coord;
+
+float get_offset(int index) {
+  // u_color_stops[5 * index] makes Intel Windows driver crash.
+  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
+  int base = 5 * index;
+  return u_color_stops[base];
+}
+
+vec4 get_color(int index) {
+  int base = 5 * index + 1;
+
+  return vec4(u_color_stops[base],
+              u_color_stops[base + 1],
+              u_color_stops[base + 2],
+              u_color_stops[base + 3]);
+}
+
+void main() {
+  // direction of point in range [-PI, PI]
+  vec2 pos = floor(coord);
+  float angle = atan(pos.y, pos.x);
+
+  // fract() does the modulo here, so now we have progress
+  // into the current conic
+  float offset = fract(angle * u_geometry.z + u_geometry.w);
+  float curr_offset;
+  float next_offset;
+
+  next_offset = get_offset(0);
+  if (offset < next_offset) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
+    return;
+  }
+
+  if (offset >= get_offset(u_num_color_stops - 1)) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
+    return;
+  }
+
+  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
+    curr_offset = next_offset;
+    next_offset = get_offset(i + 1);
+
+    if (offset < next_offset) {
+      float f = (offset - curr_offset) / (next_offset - curr_offset);
+      vec4 curr_color = gsk_premultiply(get_color(i));
+      vec4 next_color = gsk_premultiply(get_color(i + 1));
+      vec4 color = mix(curr_color, next_color, f);
+      gskSetScaledOutputColor(color, u_alpha);
+      return;
+    }
+  }
+}
diff --git a/gsk/gl/resources/cross_fade.glsl b/gsk/gl/resources/cross_fade.glsl
new file mode 100644 (file)
index 0000000..e61af59
--- /dev/null
@@ -0,0 +1,24 @@
+// VERTEX_SHADER:
+// cross_fade.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// cross_fade.glsl
+
+uniform float u_progress;
+uniform sampler2D u_source2;
+
+void main() {
+  vec4 source1 = GskTexture(u_source, vUv);  // start child
+  vec4 source2 = GskTexture(u_source2, vUv); // end child
+
+  float p_start = (1.0 - u_progress) * u_alpha;
+  float p_end = u_progress * u_alpha;
+  vec4 color = (p_start * source1) + (p_end * source2);
+  gskSetOutputColor(color);
+}
diff --git a/gsk/gl/resources/custom.glsl b/gsk/gl/resources/custom.glsl
new file mode 100644 (file)
index 0000000..6f91df6
--- /dev/null
@@ -0,0 +1,25 @@
+// VERTEX_SHADER:
+// custom.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// custom.glsl
+
+// The shader supplies:
+void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv);
+
+uniform vec2 u_size;
+uniform sampler2D u_source2;
+uniform sampler2D u_source3;
+uniform sampler2D u_source4;
+
+void main() {
+  vec4 fragColor;
+  vec2 fragCoord = vec2(vUv.x * u_size.x, (1.0-vUv.y) * u_size.y);
+  mainImage(fragColor, fragCoord, u_size, vUv);
+  gskSetOutputColor(fragColor);
+}
diff --git a/gsk/gl/resources/filled_border.glsl b/gsk/gl/resources/filled_border.glsl
new file mode 100644 (file)
index 0000000..d503dd0
--- /dev/null
@@ -0,0 +1,47 @@
+// VERTEX_SHADER:
+// filled_border.glsl
+
+uniform vec4 u_widths;
+uniform vec4[3] u_outline_rect;
+
+_OUT_ vec4 outer_color;
+_OUT_ vec4 inner_color;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  outer_color = gsk_scaled_premultiply(aColor, u_alpha);
+  inner_color = gsk_scaled_premultiply(aColor2, u_alpha);
+
+  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
+  GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths);
+
+  gsk_rounded_rect_transform(outside, u_modelview);
+  gsk_rounded_rect_transform(inside, u_modelview);
+
+  gsk_rounded_rect_encode(outside, transformed_outside_outline);
+  gsk_rounded_rect_encode(inside, transformed_inside_outline);
+}
+
+// FRAGMENT_SHADER:
+// filled_border.glsl
+
+uniform vec4[3] u_outline_rect;
+
+_IN_ vec4 outer_color;
+_IN_ vec4 inner_color;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  vec2 frag = gsk_get_frag_coord();
+  float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag);
+  float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag);
+
+  float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0);
+  float alpha2 = clamp(inner_coverage, 0.0, 1.0);
+
+  gskSetOutputColor((outer_color * alpha) + (inner_color * alpha2));
+}
diff --git a/gsk/gl/resources/inset_shadow.glsl b/gsk/gl/resources/inset_shadow.glsl
new file mode 100644 (file)
index 0000000..f052a08
--- /dev/null
@@ -0,0 +1,44 @@
+// VERTEX_SHADER:
+// inset_shadow.glsl
+
+uniform float u_spread;
+uniform vec2 u_offset;
+uniform vec4[3] u_outline_rect;
+
+_OUT_ vec4 final_color;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  final_color = gsk_scaled_premultiply(aColor, u_alpha);
+
+  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
+  GskRoundedRect inside = gsk_rounded_rect_shrink(outside, vec4(u_spread));
+
+  gsk_rounded_rect_offset(inside, u_offset);
+
+  gsk_rounded_rect_transform(outside, u_modelview);
+  gsk_rounded_rect_transform(inside, u_modelview);
+
+  gsk_rounded_rect_encode(outside, transformed_outside_outline);
+  gsk_rounded_rect_encode(inside, transformed_inside_outline);
+}
+
+// FRAGMENT_SHADER:
+// inset_shadow.glsl
+
+_IN_ vec4 final_color;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  vec2 frag = gsk_get_frag_coord();
+
+  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
+                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
+                      0.0, 1.0);
+
+  gskSetScaledOutputColor(final_color, alpha);
+}
diff --git a/gsk/gl/resources/linear_gradient.glsl b/gsk/gl/resources/linear_gradient.glsl
new file mode 100644 (file)
index 0000000..153d7af
--- /dev/null
@@ -0,0 +1,107 @@
+// VERTEX_SHADER
+// linear_gradient.glsl
+uniform vec4 u_points;
+
+_NOPERSPECTIVE_ _OUT_ vec4 info;
+
+void main() {
+  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
+
+  vec2 mv0 = u_modelview[0].xy;
+  vec2 mv1 = u_modelview[1].xy;
+  vec2 offset = aPosition - u_points.xy;
+  vec2 coord = vec2(dot(mv0, offset),
+                    dot(mv1, offset));
+
+  // Original equation:
+  // VS | maxDist = length(end - start);
+  // VS | gradient = end - start;
+  // VS | gradientLength = length(gradient);
+  // FS | pos = frag_coord - start
+  // FS | proj = (dot(gradient, pos) / (gradientLength * gradientLength)) * gradient
+  // FS | offset = length(proj) / maxDist
+
+  // Simplified formula derivation:
+  // 1. Notice that maxDist = gradientLength:
+  // offset = length(proj) / gradientLength
+  // 2. Let gnorm = gradient / gradientLength, then:
+  // proj = (dot(gnorm * gradientLength, pos) / (gradientLength * gradientLength)) * (gnorm * gradientLength) =
+  //      = dot(gnorm, pos) * gnorm
+  // 3. Since gnorm is unit length then:
+  // length(proj) = length(dot(gnorm, pos) * gnorm) = dot(gnorm, pos)
+  // 4. We can avoid the FS division by passing a scaled pos from the VS:
+  // offset = dot(gnorm, pos) / gradientLength = dot(gnorm, pos / gradientLength)
+  // 5. 1.0 / length(gradient) is inversesqrt(dot(gradient, gradient)) in GLSL
+  vec2 gradient = vec2(dot(mv0, u_points.zw),
+                       dot(mv1, u_points.zw));
+  float rcp_gradient_length = inversesqrt(dot(gradient, gradient));
+
+  info = rcp_gradient_length * vec4(coord, gradient);
+}
+
+// FRAGMENT_SHADER:
+// linear_gradient.glsl
+
+#define MAX_COLOR_STOPS 6
+
+#ifdef GSK_LEGACY
+uniform int u_num_color_stops;
+#else
+uniform highp int u_num_color_stops; // Why? Because it works like this.
+#endif
+
+uniform float u_color_stops[MAX_COLOR_STOPS * 5];
+uniform bool u_repeat;
+
+_NOPERSPECTIVE_ _IN_ vec4 info;
+
+float get_offset(int index) {
+  // u_color_stops[5 * index] makes Intel Windows driver crash.
+  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
+  int base = 5 * index;
+  return u_color_stops[base];
+}
+
+vec4 get_color(int index) {
+  int base = 5 * index + 1;
+
+  return vec4(u_color_stops[base],
+              u_color_stops[base + 1],
+              u_color_stops[base + 2],
+              u_color_stops[base + 3]);
+}
+
+void main() {
+  float offset = dot(info.xy, info.zw);
+  float curr_offset;
+  float next_offset;
+
+  if (u_repeat) {
+    offset = fract(offset);
+  }
+
+  next_offset = get_offset(0);
+  if (offset < next_offset) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
+    return;
+  }
+
+  if (offset >= get_offset(u_num_color_stops - 1)) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
+    return;
+  }
+
+  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
+    curr_offset = next_offset;
+    next_offset = get_offset(i + 1);
+
+    if (offset < next_offset) {
+      float f = (offset - curr_offset) / (next_offset - curr_offset);
+      vec4 curr_color = gsk_premultiply(get_color(i));
+      vec4 next_color = gsk_premultiply(get_color(i + 1));
+      vec4 color = mix(curr_color, next_color, f);
+      gskSetScaledOutputColor(color, u_alpha);
+      return;
+    }
+  }
+}
diff --git a/gsk/gl/resources/outset_shadow.glsl b/gsk/gl/resources/outset_shadow.glsl
new file mode 100644 (file)
index 0000000..44b05aa
--- /dev/null
@@ -0,0 +1,34 @@
+// VERTEX_SHADER:
+// outset_shadow.glsl
+
+uniform vec4[3] u_outline_rect;
+
+_OUT_ vec4 final_color;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outline;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+
+  final_color = gsk_scaled_premultiply(aColor, u_alpha);
+
+  GskRoundedRect outline = gsk_create_rect(u_outline_rect);
+  gsk_rounded_rect_transform(outline, u_modelview);
+  gsk_rounded_rect_encode(outline, transformed_outline);
+}
+
+// FRAGMENT_SHADER:
+// outset_shadow.glsl
+
+_IN_ vec4 final_color;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outline;
+
+void main() {
+  vec2 frag = gsk_get_frag_coord();
+  float alpha = GskTexture(u_source, vUv).a;
+
+  alpha *= (1.0 -  clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outline), frag), 0.0, 1.0));
+
+  gskSetScaledOutputColor(final_color, alpha);
+}
diff --git a/gsk/gl/resources/preamble.fs.glsl b/gsk/gl/resources/preamble.fs.glsl
new file mode 100644 (file)
index 0000000..c1c5a95
--- /dev/null
@@ -0,0 +1,162 @@
+uniform sampler2D u_source;
+uniform mat4 u_projection;
+uniform mat4 u_modelview;
+uniform float u_alpha;
+uniform vec4 u_viewport;
+uniform vec4[3] u_clip_rect;
+
+#if defined(GSK_LEGACY)
+_OUT_ vec4 outputColor;
+#elif !defined(GSK_GLES)
+_OUT_ vec4 outputColor;
+#endif
+
+_IN_ vec2 vUv;
+
+
+GskRoundedRect gsk_decode_rect(_GSK_ROUNDED_RECT_UNIFORM_ r)
+{
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+  return GskRoundedRect(r[0], r[1], r[2]);
+#else
+  return r;
+#endif
+}
+
+float
+gsk_ellipsis_dist (vec2 p, vec2 radius)
+{
+  if (radius == vec2(0, 0))
+    return 0.0;
+
+  vec2 p0 = p / radius;
+  vec2 p1 = 2.0 * p0 / radius;
+
+  return (dot(p0, p0) - 1.0) / length (p1);
+}
+
+float
+gsk_ellipsis_coverage (vec2 point, vec2 center, vec2 radius)
+{
+  float d = gsk_ellipsis_dist (point - center, radius);
+  return clamp (0.5 - d, 0.0, 1.0);
+}
+
+float
+gsk_rounded_rect_coverage (GskRoundedRect r, vec2 p)
+{
+  if (p.x < r.bounds.x || p.y < r.bounds.y ||
+      p.x >= r.bounds.z || p.y >= r.bounds.w)
+    return 0.0;
+
+  vec2 ref_tl = r.corner_points1.xy;
+  vec2 ref_tr = r.corner_points1.zw;
+  vec2 ref_br = r.corner_points2.xy;
+  vec2 ref_bl = r.corner_points2.zw;
+
+  if (p.x >= ref_tl.x && p.x >= ref_bl.x &&
+      p.x <= ref_tr.x && p.x <= ref_br.x)
+    return 1.0;
+
+  if (p.y >= ref_tl.y && p.y >= ref_tr.y &&
+      p.y <= ref_bl.y && p.y <= ref_br.y)
+    return 1.0;
+
+  vec2 rad_tl = r.corner_points1.xy - r.bounds.xy;
+  vec2 rad_tr = r.corner_points1.zw - r.bounds.zy;
+  vec2 rad_br = r.corner_points2.xy - r.bounds.zw;
+  vec2 rad_bl = r.corner_points2.zw - r.bounds.xw;
+
+  float d_tl = gsk_ellipsis_coverage(p, ref_tl, rad_tl);
+  float d_tr = gsk_ellipsis_coverage(p, ref_tr, rad_tr);
+  float d_br = gsk_ellipsis_coverage(p, ref_br, rad_br);
+  float d_bl = gsk_ellipsis_coverage(p, ref_bl, rad_bl);
+
+  vec4 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl);
+
+  bvec4 is_out = bvec4(p.x < ref_tl.x && p.y < ref_tl.y,
+                       p.x > ref_tr.x && p.y < ref_tr.y,
+                       p.x > ref_br.x && p.y > ref_br.y,
+                       p.x < ref_bl.x && p.y > ref_bl.y);
+
+  return 1.0 - dot(vec4(is_out), corner_coverages);
+}
+
+float
+gsk_rect_coverage (vec4 r, vec2 p)
+{
+  if (p.x < r.x || p.y < r.y ||
+      p.x >= r.z || p.y >= r.w)
+    return 0.0;
+
+  return 1.0;
+}
+
+vec4 GskTexture(sampler2D sampler, vec2 texCoords) {
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+  return texture2D(sampler, texCoords);
+#else
+  return texture(sampler, texCoords);
+#endif
+}
+
+#ifdef GSK_GL3
+layout(origin_upper_left) in vec4 gl_FragCoord;
+#endif
+
+vec2 gsk_get_frag_coord() {
+  vec2 fc = gl_FragCoord.xy;
+
+#ifdef GSK_GL3
+  fc += u_viewport.xy;
+#else
+  fc.x += u_viewport.x;
+  fc.y = (u_viewport.y + u_viewport.w) - fc.y;
+#endif
+
+  return fc;
+}
+
+void gskSetOutputColor(vec4 color) {
+  vec4 result;
+
+#if defined(NO_CLIP)
+  result = color;
+#elif defined(RECT_CLIP)
+  float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect),
+                                     gsk_get_frag_coord());
+  result = color * coverage;
+#else
+  float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect),
+                                             gsk_get_frag_coord());
+  result = color * coverage;
+#endif
+
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+  gl_FragColor = result;
+#else
+  outputColor = result;
+#endif
+}
+
+void gskSetScaledOutputColor(vec4 color, float alpha) {
+  vec4 result;
+
+#if defined(NO_CLIP)
+  result = color * alpha;
+#elif defined(RECT_CLIP)
+  float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect),
+                                     gsk_get_frag_coord());
+  result = color * (alpha * coverage);
+#else
+  float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect),
+                                             gsk_get_frag_coord());
+  result = color * (alpha * coverage);
+#endif
+
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+  gl_FragColor = result;
+#else
+  outputColor = result;
+#endif
+}
diff --git a/gsk/gl/resources/preamble.glsl b/gsk/gl/resources/preamble.glsl
new file mode 100644 (file)
index 0000000..8bc007b
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef GSK_LEGACY
+precision highp float;
+#endif
+
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+#define _OUT_ varying
+#define _IN_ varying
+#define _NOPERSPECTIVE_
+#define _GSK_ROUNDED_RECT_UNIFORM_ vec4[3]
+#else
+#define _OUT_ out
+#define _IN_ in
+#define _NOPERSPECTIVE_ noperspective
+#define _GSK_ROUNDED_RECT_UNIFORM_ GskRoundedRect
+#endif
+
+
+struct GskRoundedRect
+{
+  vec4 bounds; // Top left and bottom right
+  // Look, arrays can't be in structs if you want to return the struct
+  // from a function in gles or whatever. Just kill me.
+  vec4 corner_points1; // xy = top left, zw = top right
+  vec4 corner_points2; // xy = bottom right, zw = bottom left
+};
+
+// Transform from a C GskRoundedRect to what we need.
+GskRoundedRect
+gsk_create_rect(vec4[3] data)
+{
+  vec4 bounds = vec4(data[0].xy, data[0].xy + data[0].zw);
+
+  vec4 corner_points1 = vec4(bounds.xy + data[1].xy,
+                             bounds.zy + vec2(data[1].zw * vec2(-1, 1)));
+  vec4 corner_points2 = vec4(bounds.zw + (data[2].xy * vec2(-1, -1)),
+                             bounds.xw + vec2(data[2].zw * vec2(1, -1)));
+
+  return GskRoundedRect(bounds, corner_points1, corner_points2);
+}
+
+vec4
+gsk_get_bounds(vec4[3] data)
+{
+  return vec4(data[0].xy, data[0].xy + data[0].zw);
+}
+
+vec4 gsk_premultiply(vec4 c) {
+  return vec4(c.rgb * c.a, c.a);
+}
+
+vec4 gsk_scaled_premultiply(vec4 c, float s) {
+  // Fast version of gsk_premultiply(c) * s
+  // 4 muls instead of 7
+  float a = s * c.a;
+
+  return vec4(c.rgb * a, a);
+}
diff --git a/gsk/gl/resources/preamble.vs.glsl b/gsk/gl/resources/preamble.vs.glsl
new file mode 100644 (file)
index 0000000..758bca8
--- /dev/null
@@ -0,0 +1,73 @@
+uniform mat4 u_projection;
+uniform mat4 u_modelview;
+uniform float u_alpha;
+
+#if defined(GSK_GLES) || defined(GSK_LEGACY)
+attribute vec2 aPosition;
+attribute vec2 aUv;
+attribute vec4 aColor;
+attribute vec4 aColor2;
+_OUT_ vec2 vUv;
+#else
+_IN_ vec2 aPosition;
+_IN_ vec2 aUv;
+_IN_ vec4 aColor;
+_IN_ vec4 aColor2;
+_OUT_ vec2 vUv;
+#endif
+
+// amount is: top, right, bottom, left
+GskRoundedRect
+gsk_rounded_rect_shrink (GskRoundedRect r, vec4 amount)
+{
+  vec4 new_bounds = r.bounds + vec4(1.0,1.0,-1.0,-1.0) * amount.wxyz;
+  vec4 new_corner_points1 = r.corner_points1;
+  vec4 new_corner_points2 = r.corner_points2;
+
+  if (r.corner_points1.xy == r.bounds.xy) new_corner_points1.xy = new_bounds.xy;
+  if (r.corner_points1.zw == r.bounds.zy) new_corner_points1.zw = new_bounds.zy;
+  if (r.corner_points2.xy == r.bounds.zw) new_corner_points2.xy = new_bounds.zw;
+  if (r.corner_points2.zw == r.bounds.xw) new_corner_points2.zw = new_bounds.xw;
+
+  return GskRoundedRect (new_bounds, new_corner_points1, new_corner_points2);
+}
+
+void
+gsk_rounded_rect_offset(inout GskRoundedRect r, vec2 offset)
+{
+  r.bounds.xy += offset;
+  r.bounds.zw += offset;
+  r.corner_points1.xy += offset;
+  r.corner_points1.zw += offset;
+  r.corner_points2.xy += offset;
+  r.corner_points2.zw += offset;
+}
+
+void gsk_rounded_rect_transform(inout GskRoundedRect r, mat4 mat)
+{
+  r.bounds.xy = (mat * vec4(r.bounds.xy, 0.0, 1.0)).xy;
+  r.bounds.zw = (mat * vec4(r.bounds.zw, 0.0, 1.0)).xy;
+
+  r.corner_points1.xy = (mat * vec4(r.corner_points1.xy, 0.0, 1.0)).xy;
+  r.corner_points1.zw = (mat * vec4(r.corner_points1.zw, 0.0, 1.0)).xy;
+
+  r.corner_points2.xy = (mat * vec4(r.corner_points2.xy, 0.0, 1.0)).xy;
+  r.corner_points2.zw = (mat * vec4(r.corner_points2.zw, 0.0, 1.0)).xy;
+}
+
+#if defined(GSK_LEGACY)
+// Can't have out or inout array parameters...
+#define gsk_rounded_rect_encode(r, uni) uni[0] = r.bounds; uni[1] = r.corner_points1; uni[2] = r.corner_points2;
+#else
+void gsk_rounded_rect_encode(GskRoundedRect r, out _GSK_ROUNDED_RECT_UNIFORM_ out_r)
+{
+#if defined(GSK_GLES)
+  out_r[0] = r.bounds;
+  out_r[1] = r.corner_points1;
+  out_r[2] = r.corner_points2;
+#else
+  out_r = r;
+#endif
+}
+
+#endif
diff --git a/gsk/gl/resources/radial_gradient.glsl b/gsk/gl/resources/radial_gradient.glsl
new file mode 100644 (file)
index 0000000..4311fc5
--- /dev/null
@@ -0,0 +1,87 @@
+// VERTEX_SHADER
+// radial_gradient.glsl
+
+uniform vec4 u_geometry;
+
+_NOPERSPECTIVE_ _OUT_ vec2 coord;
+
+void main() {
+  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
+
+  vec2 mv0 = u_modelview[0].xy;
+  vec2 mv1 = u_modelview[1].xy;
+  vec2 offset = aPosition - u_geometry.xy;
+  vec2 dir = vec2(dot(mv0, offset),
+                  dot(mv1, offset));
+
+  coord = dir * u_geometry.zw;
+}
+
+// FRAGMENT_SHADER:
+// radial_gradient.glsl
+
+#define MAX_COLOR_STOPS 6
+
+#ifdef GSK_LEGACY
+uniform int u_num_color_stops;
+#else
+uniform highp int u_num_color_stops;
+#endif
+
+uniform bool u_repeat;
+uniform vec2 u_range;
+uniform float u_color_stops[MAX_COLOR_STOPS * 5];
+
+_NOPERSPECTIVE_ _IN_ vec2 coord;
+
+float get_offset(int index) {
+  // u_color_stops[5 * index] makes Intel Windows driver crash.
+  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
+  int base = 5 * index;
+  return u_color_stops[base];
+}
+
+vec4 get_color(int index) {
+  int base = 5 * index + 1;
+
+  return vec4(u_color_stops[base],
+              u_color_stops[base + 1],
+              u_color_stops[base + 2],
+              u_color_stops[base + 3]);
+}
+
+void main() {
+  // Reverse scale
+  float offset = length(coord) * u_range.x + u_range.y;
+  float curr_offset;
+  float next_offset;
+
+  if (u_repeat) {
+    offset = fract(offset);
+  }
+
+  next_offset = get_offset(0);
+  if (offset < next_offset) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
+    return;
+  }
+
+  if (offset >= get_offset(u_num_color_stops - 1)) {
+    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
+    return;
+  }
+
+  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
+    curr_offset = next_offset;
+    next_offset = get_offset(i + 1);
+
+    if (offset < next_offset) {
+      float f = (offset - curr_offset) / (next_offset - curr_offset);
+      vec4 curr_color = gsk_premultiply(get_color(i));
+      vec4 next_color = gsk_premultiply(get_color(i + 1));
+      vec4 color = mix(curr_color, next_color, f);
+      gskSetScaledOutputColor(color, u_alpha);
+      return;
+    }
+  }
+}
diff --git a/gsk/gl/resources/repeat.glsl b/gsk/gl/resources/repeat.glsl
new file mode 100644 (file)
index 0000000..1b811df
--- /dev/null
@@ -0,0 +1,44 @@
+// VERTEX_SHADER:
+// repeat.glsl
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  vUv = vec2(aUv.x, aUv.y);
+}
+
+// FRAGMENT_SHADER:
+// repeat.glsl
+
+uniform vec4 u_child_bounds;
+uniform vec4 u_texture_rect;
+
+float wrap(float f, float wrap_for) {
+  return mod(f, wrap_for);
+}
+
+/* We get the texture coordinates via vUv,
+ * but that might be on a texture atlas, so we need to do the
+ * wrapping ourselves.
+ */
+void main() {
+
+  /* We map the texture coordinate to [1;0], then wrap it and scale the result again */
+
+  float tw = u_texture_rect.z - u_texture_rect.x;
+  float th = u_texture_rect.w - u_texture_rect.y;
+
+  float mapped_x = (vUv.x - u_texture_rect.x) / tw;
+  float mapped_y = (vUv.y - u_texture_rect.y) / th;
+
+  float wrapped_x = wrap(u_child_bounds.x + mapped_x * u_child_bounds.z, 1.0);
+  float wrapped_y = wrap(u_child_bounds.y + mapped_y * u_child_bounds.w, 1.0);
+
+  vec2 tp;
+  tp.x = u_texture_rect.x + (wrapped_x * tw);
+  tp.y = u_texture_rect.y + (wrapped_y * th);
+
+  vec4 diffuse = GskTexture(u_source, tp);
+
+  gskSetScaledOutputColor(diffuse, u_alpha);
+}
diff --git a/gsk/gl/resources/unblurred_outset_shadow.glsl b/gsk/gl/resources/unblurred_outset_shadow.glsl
new file mode 100644 (file)
index 0000000..cd44212
--- /dev/null
@@ -0,0 +1,44 @@
+// VERTEX_SHADER:
+// unblurred_outset_shadow.glsl
+
+uniform float u_spread;
+uniform vec2 u_offset;
+uniform vec4[3] u_outline_rect;
+
+_OUT_ vec4 final_color;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
+
+  final_color = gsk_premultiply(aColor) * u_alpha;
+
+  GskRoundedRect inside = gsk_create_rect(u_outline_rect);
+  GskRoundedRect outside = gsk_rounded_rect_shrink(inside, vec4(- u_spread));
+
+  gsk_rounded_rect_offset(outside, u_offset);
+
+  gsk_rounded_rect_transform(outside, u_modelview);
+  gsk_rounded_rect_transform(inside, u_modelview);
+
+  gsk_rounded_rect_encode(outside, transformed_outside_outline);
+  gsk_rounded_rect_encode(inside, transformed_inside_outline);
+}
+
+// FRAGMENT_SHADER:
+// unblurred_outset_shadow.glsl
+_IN_ vec4 final_color;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
+_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
+
+void main() {
+  vec2 frag = gsk_get_frag_coord();
+
+  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
+                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
+                      0.0, 1.0);
+
+  gskSetScaledOutputColor(final_color, alpha);
+}
+
diff --git a/gsk/gl/stb_rect_pack.c b/gsk/gl/stb_rect_pack.c
new file mode 100644 (file)
index 0000000..ff65846
--- /dev/null
@@ -0,0 +1,431 @@
+
+#include "stb_rect_pack.h"
+#define STB_RECT_PACK_IMPLEMENTATION
+//////////////////////////////////////////////////////////////////////////////
+//
+//     IMPLEMENTATION SECTION
+//
+
+#ifdef STB_RECT_PACK_IMPLEMENTATION
+#ifndef STBRP_SORT
+#include <stdlib.h>
+#define STBRP_SORT qsort
+#endif
+
+#ifndef STBRP_ASSERT
+#include <assert.h>
+#define STBRP_ASSERT assert
+#endif
+
+#ifdef _MSC_VER
+#define STBRP__NOTUSED(v)  (void)(v)
+#else
+#define STBRP__NOTUSED(v)  (void)sizeof(v)
+#endif
+
+enum
+{
+   STBRP__INIT_skyline = 1
+};
+
+STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
+{
+   switch (context->init_mode) {
+      case STBRP__INIT_skyline:
+         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
+         context->heuristic = heuristic;
+         break;
+      default:
+         STBRP_ASSERT(0);
+   }
+}
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
+{
+   if (allow_out_of_mem)
+      // if it's ok to run out of memory, then don't bother aligning them;
+      // this gives better packing, but may fail due to OOM (even though
+      // the rectangles easily fit). @TODO a smarter approach would be to only
+      // quantize once we've hit OOM, then we could get rid of this parameter.
+      context->align = 1;
+   else {
+      // if it's not ok to run out of memory, then quantize the widths
+      // so that num_nodes is always enough nodes.
+      //
+      // I.e. num_nodes * align >= width
+      //                  align >= width / num_nodes
+      //                  align = ceil(width/num_nodes)
+
+      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
+   }
+}
+
+STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
+{
+   int i;
+#ifndef STBRP_LARGE_RECTS
+   STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
+#endif
+
+   for (i=0; i < num_nodes-1; ++i)
+      nodes[i].next = &nodes[i+1];
+   nodes[i].next = NULL;
+   context->init_mode = STBRP__INIT_skyline;
+   context->heuristic = STBRP_HEURISTIC_Skyline_default;
+   context->free_head = &nodes[0];
+   context->active_head = &context->extra[0];
+   context->width = width;
+   context->height = height;
+   context->num_nodes = num_nodes;
+   stbrp_setup_allow_out_of_mem(context, 0);
+
+   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
+   context->extra[0].x = 0;
+   context->extra[0].y = 0;
+   context->extra[0].next = &context->extra[1];
+   context->extra[1].x = (stbrp_coord) width;
+#ifdef STBRP_LARGE_RECTS
+   context->extra[1].y = (1<<30);
+#else
+   context->extra[1].y = 65535;
+#endif
+   context->extra[1].next = NULL;
+}
+
+// find minimum y position if it starts at x1
+static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
+{
+   stbrp_node *node = first;
+   int x1 = x0 + width;
+   int min_y, visited_width, waste_area;
+
+   STBRP__NOTUSED(c);
+
+   STBRP_ASSERT(first->x <= x0);
+
+   #if 0
+   // skip in case we're past the node
+   while (node->next->x <= x0)
+      ++node;
+   #else
+   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
+   #endif
+
+   STBRP_ASSERT(node->x <= x0);
+
+   min_y = 0;
+   waste_area = 0;
+   visited_width = 0;
+   while (node->x < x1) {
+      if (node->y > min_y) {
+         // raise min_y higher.
+         // we've accounted for all waste up to min_y,
+         // but we'll now add more waste for everything we've visited
+         waste_area += visited_width * (node->y - min_y);
+         min_y = node->y;
+         // the first time through, visited_width might be reduced
+         if (node->x < x0)
+            visited_width += node->next->x - x0;
+         else
+            visited_width += node->next->x - node->x;
+      } else {
+         // add waste area
+         int under_width = node->next->x - node->x;
+         if (under_width + visited_width > width)
+            under_width = width - visited_width;
+         waste_area += under_width * (min_y - node->y);
+         visited_width += under_width;
+      }
+      node = node->next;
+   }
+
+   *pwaste = waste_area;
+   return min_y;
+}
+
+typedef struct
+{
+   int x,y;
+   stbrp_node **prev_link;
+} stbrp__findresult;
+
+static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
+{
+   int best_waste = (1<<30), best_x, best_y = (1 << 30);
+   stbrp__findresult fr;
+   stbrp_node **prev, *node, *tail, **best = NULL;
+
+   // align to multiple of c->align
+   width = (width + c->align - 1);
+   width -= width % c->align;
+   STBRP_ASSERT(width % c->align == 0);
+
+   node = c->active_head;
+   prev = &c->active_head;
+   while (node->x + width <= c->width) {
+      int y,waste;
+      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
+      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
+         // bottom left
+         if (y < best_y) {
+            best_y = y;
+            best = prev;
+         }
+      } else {
+         // best-fit
+         if (y + height <= c->height) {
+            // can only use it if it first vertically
+            if (y < best_y || (y == best_y && waste < best_waste)) {
+               best_y = y;
+               best_waste = waste;
+               best = prev;
+            }
+         }
+      }
+      prev = &node->next;
+      node = node->next;
+   }
+
+   best_x = (best == NULL) ? 0 : (*best)->x;
+
+   // if doing best-fit (BF), we also have to try aligning right edge to each node position
+   //
+   // e.g, if fitting
+   //
+   //     ____________________
+   //    |____________________|
+   //
+   //            into
+   //
+   //   |                         |
+   //   |             ____________|
+   //   |____________|
+   //
+   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
+   //
+   // This makes BF take about 2x the time
+
+   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
+      tail = c->active_head;
+      node = c->active_head;
+      prev = &c->active_head;
+      // find first node that's admissible
+      while (tail->x < width)
+         tail = tail->next;
+      while (tail) {
+         int xpos = tail->x - width;
+         int y,waste;
+         STBRP_ASSERT(xpos >= 0);
+         // find the left position that matches this
+         while (node->next->x <= xpos) {
+            prev = &node->next;
+            node = node->next;
+         }
+         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
+         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
+         if (y + height < c->height) {
+            if (y <= best_y) {
+               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
+                  best_x = xpos;
+                  STBRP_ASSERT(y <= best_y);
+                  best_y = y;
+                  best_waste = waste;
+                  best = prev;
+               }
+            }
+         }
+         tail = tail->next;
+      }
+   }
+
+   fr.prev_link = best;
+   fr.x = best_x;
+   fr.y = best_y;
+   return fr;
+}
+
+static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
+{
+   // find best position according to heuristic
+   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
+   stbrp_node *node, *cur;
+
+   // bail if:
+   //    1. it failed
+   //    2. the best node doesn't fit (we don't always check this)
+   //    3. we're out of memory
+   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
+      res.prev_link = NULL;
+      return res;
+   }
+
+   // on success, create new node
+   node = context->free_head;
+   node->x = (stbrp_coord) res.x;
+   node->y = (stbrp_coord) (res.y + height);
+
+   context->free_head = node->next;
+
+   // insert the new node into the right starting point, and
+   // let 'cur' point to the remaining nodes needing to be
+   // stiched back in
+
+   cur = *res.prev_link;
+   if (cur->x < res.x) {
+      // preserve the existing one, so start testing with the next one
+      stbrp_node *next = cur->next;
+      cur->next = node;
+      cur = next;
+   } else {
+      *res.prev_link = node;
+   }
+
+   // from here, traverse cur and free the nodes, until we get to one
+   // that shouldn't be freed
+   while (cur->next && cur->next->x <= res.x + width) {
+      stbrp_node *next = cur->next;
+      // move the current node to the free list
+      cur->next = context->free_head;
+      context->free_head = cur;
+      cur = next;
+   }
+
+   // stitch the list back in
+   node->next = cur;
+
+   if (cur->x < res.x + width)
+      cur->x = (stbrp_coord) (res.x + width);
+
+#ifdef _DEBUG
+   cur = context->active_head;
+   while (cur->x < context->width) {
+      STBRP_ASSERT(cur->x < cur->next->x);
+      cur = cur->next;
+   }
+   STBRP_ASSERT(cur->next == NULL);
+
+   {
+      int count=0;
+      cur = context->active_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      cur = context->free_head;
+      while (cur) {
+         cur = cur->next;
+         ++count;
+      }
+      STBRP_ASSERT(count == context->num_nodes+2);
+   }
+#endif
+
+   return res;
+}
+
+static int rect_height_compare(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   if (p->h > q->h)
+      return -1;
+   if (p->h < q->h)
+      return  1;
+   return (p->w > q->w) ? -1 : (p->w < q->w);
+}
+
+static int rect_original_order(const void *a, const void *b)
+{
+   const stbrp_rect *p = (const stbrp_rect *) a;
+   const stbrp_rect *q = (const stbrp_rect *) b;
+   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
+}
+
+#ifdef STBRP_LARGE_RECTS
+#define STBRP__MAXVAL  0xffffffff
+#else
+#define STBRP__MAXVAL  0xffff
+#endif
+
+STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
+{
+   int i, all_rects_packed = 1;
+
+   // we use the 'was_packed' field internally to allow sorting/unsorting
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = i;
+   }
+
+   // sort according to heuristic
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
+
+   for (i=0; i < num_rects; ++i) {
+      if (rects[i].w == 0 || rects[i].h == 0) {
+         rects[i].x = rects[i].y = 0;  // empty rect needs no space
+      } else {
+         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
+         if (fr.prev_link) {
+            rects[i].x = (stbrp_coord) fr.x;
+            rects[i].y = (stbrp_coord) fr.y;
+         } else {
+            rects[i].x = rects[i].y = STBRP__MAXVAL;
+         }
+      }
+   }
+
+   // unsort
+   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
+
+   // set was_packed flags and all_rects_packed status
+   for (i=0; i < num_rects; ++i) {
+      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
+      if (!rects[i].was_packed)
+         all_rects_packed = 0;
+   }
+
+   // return the all_rects_packed status
+   return all_rects_packed;
+}
+#endif
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/
diff --git a/gsk/gl/stb_rect_pack.h b/gsk/gl/stb_rect_pack.h
new file mode 100644 (file)
index 0000000..3ecb1c2
--- /dev/null
@@ -0,0 +1,191 @@
+// stb_rect_pack.h - v0.99 - public domain - rectangle packing
+// Sean Barrett 2014
+//
+// Useful for e.g. packing rectangular textures into an atlas.
+// Does not do rotation.
+//
+// Not necessarily the awesomest packing method, but better than
+// the totally naive one in stb_truetype (which is primarily what
+// this is meant to replace).
+//
+// Has only had a few tests run, may have issues.
+//
+// More docs to come.
+//
+// No memory allocations; uses qsort() and assert() from stdlib.
+// Can override those by defining STBRP_SORT and STBRP_ASSERT.
+//
+// This library currently uses the Skyline Bottom-Left algorithm.
+//
+// Please note: better rectangle packers are welcome! Please
+// implement them to the same API, but with a different init
+// function.
+//
+// Credits
+//
+//  Library
+//    Sean Barrett
+//  Minor features
+//    Martins Mozeiko
+//    github:IntellectualKitty
+//
+//  Bugfixes / warning fixes
+//    Jeremy Jaussaud
+//
+// Version history:
+//
+//     0.99  (2019-02-07)  warning fixes
+//     0.11  (2017-03-03)  return packing success/fail result
+//     0.10  (2016-10-25)  remove cast-away-const to avoid warnings
+//     0.09  (2016-08-27)  fix compiler warnings
+//     0.08  (2015-09-13)  really fix bug with empty rects (w=0 or h=0)
+//     0.07  (2015-09-13)  fix bug with empty rects (w=0 or h=0)
+//     0.06  (2015-04-15)  added STBRP_SORT to allow replacing qsort
+//     0.05:  added STBRP_ASSERT to allow replacing assert
+//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support
+//     0.01:  initial release
+//
+// LICENSE
+//
+//   See end of file for license information.
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//       INCLUDE SECTION
+//
+
+#ifndef STB_INCLUDE_STB_RECT_PACK_H
+#define STB_INCLUDE_STB_RECT_PACK_H
+
+#define STB_RECT_PACK_VERSION  1
+
+#ifdef STBRP_STATIC
+#define STBRP_DEF static
+#else
+#define STBRP_DEF extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct stbrp_context stbrp_context;
+typedef struct stbrp_node    stbrp_node;
+typedef struct stbrp_rect    stbrp_rect;
+
+#ifdef STBRP_LARGE_RECTS
+typedef int            stbrp_coord;
+#else
+typedef unsigned short stbrp_coord;
+#endif
+
+STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
+// Assign packed locations to rectangles. The rectangles are of type
+// 'stbrp_rect' defined below, stored in the array 'rects', and there
+// are 'num_rects' many of them.
+//
+// Rectangles which are successfully packed have the 'was_packed' flag
+// set to a non-zero value and 'x' and 'y' store the minimum location
+// on each axis (i.e. bottom-left in cartesian coordinates, top-left
+// if you imagine y increasing downwards). Rectangles which do not fit
+// have the 'was_packed' flag set to 0.
+//
+// You should not try to access the 'rects' array from another thread
+// while this function is running, as the function temporarily reorders
+// the array while it executes.
+//
+// To pack into another rectangle, you need to call stbrp_init_target
+// again. To continue packing into the same rectangle, you can call
+// this function again. Calling this multiple times with multiple rect
+// arrays will probably produce worse packing results than calling it
+// a single time with the full rectangle array, but the option is
+// available.
+//
+// The function returns 1 if all of the rectangles were successfully
+// packed and 0 otherwise.
+
+struct stbrp_rect
+{
+   // reserved for your use:
+   int            id;
+
+   // input:
+   stbrp_coord    w, h;
+
+   // output:
+   stbrp_coord    x, y;
+   int            was_packed;  // non-zero if valid packing
+
+}; // 16 bytes, nominally
+
+
+STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
+// Initialize a rectangle packer to:
+//    pack a rectangle that is 'width' by 'height' in dimensions
+//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long
+//
+// You must call this function every time you start packing into a new target.
+//
+// There is no "shutdown" function. The 'nodes' memory must stay valid for
+// the following stbrp_pack_rects() call (or calls), but can be freed after
+// the call (or calls) finish.
+//
+// Note: to guarantee best results, either:
+//       1. make sure 'num_nodes' >= 'width'
+//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
+//
+// If you don't do either of the above things, widths will be quantized to multiples
+// of small integers to guarantee the algorithm doesn't run out of temporary storage.
+//
+// If you do #2, then the non-quantized algorithm will be used, but the algorithm
+// may run out of temporary storage and be unable to pack some rectangles.
+
+STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
+// Optionally call this function after init but before doing any packing to
+// change the handling of the out-of-temp-memory scenario, described above.
+// If you call init again, this will be reset to the default (false).
+
+
+STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
+// Optionally select which packing heuristic the library should use. Different
+// heuristics will produce better/worse results for different data sets.
+// If you call init again, this will be reset to the default.
+
+enum
+{
+   STBRP_HEURISTIC_Skyline_default=0,
+   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
+   STBRP_HEURISTIC_Skyline_BF_sortHeight
+};
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// the details of the following structures don't matter to you, but they must
+// be visible so you can handle the memory allocations for them
+
+struct stbrp_node
+{
+   stbrp_coord  x,y;
+   stbrp_node  *next;
+};
+
+struct stbrp_context
+{
+   int width;
+   int height;
+   int align;
+   int init_mode;
+   int heuristic;
+   int num_nodes;
+   stbrp_node *active_head;
+   stbrp_node *free_head;
+   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
index af6704a5561e94013dfd7cf1a98d7f3ef912d22c..9394c278cc9c4052a7513bc6786a562160825687 100644 (file)
 #include "gskglshaderprivate.h"
 #include "gskdebugprivate.h"
 
-#include "ngl/gsknglrendererprivate.h"
+#include "gl/gskglrendererprivate.h"
 
 static GskGLUniformType
 uniform_type_from_glsl (const char *str)
@@ -543,8 +543,8 @@ gsk_gl_shader_compile (GskGLShader  *shader,
 {
   g_return_val_if_fail (GSK_IS_GL_SHADER (shader), FALSE);
 
-  if (GSK_IS_NGL_RENDERER (renderer))
-    return gsk_ngl_renderer_try_compile_gl_shader (GSK_NGL_RENDERER (renderer), shader, error);
+  if (GSK_IS_GL_RENDERER (renderer))
+    return gsk_gl_renderer_try_compile_gl_shader (GSK_GL_RENDERER (renderer), shader, error);
 
   g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                "The renderer does not support gl shaders");
index 77fc9a0dfa769d047b857f231d772e8664648042..7c35b051f92b1653b03a2eb75c9ce81cc58cd509 100644 (file)
@@ -38,7 +38,7 @@
 
 #include "gskcairorenderer.h"
 #include "gskdebugprivate.h"
-#include "ngl/gsknglrenderer.h"
+#include "gl/gskglrenderer.h"
 #include "gskprofilerprivate.h"
 #include "gskrendernodeprivate.h"
 
@@ -505,8 +505,8 @@ get_renderer_for_name (const char *renderer_name)
   else if (g_ascii_strcasecmp (renderer_name, "cairo") == 0)
     return GSK_TYPE_CAIRO_RENDERER;
   else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0 ||
-           g_ascii_strcasecmp (renderer_name, "ngl") == 0)
-    return GSK_TYPE_NGL_RENDERER;
+           g_ascii_strcasecmp (renderer_name, "gl") == 0)
+    return GSK_TYPE_GL_RENDERER;
 #ifdef GDK_RENDERING_VULKAN
   else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0)
     return GSK_TYPE_VULKAN_RENDERER;
@@ -520,8 +520,8 @@ get_renderer_for_name (const char *renderer_name)
       g_print ("broadway - Disabled during GTK build\n");
 #endif
       g_print ("   cairo - Use the Cairo fallback renderer\n");
-      g_print ("  opengl - Use the default OpenGL renderer\n");
-      g_print ("     ngl - An OpenGL renderer\n");
+      g_print ("  opengl - Use the OpenGL renderer\n");
+      g_print ("      gl - Use the OpenGL renderer\n");
 #ifdef GDK_RENDERING_VULKAN
       g_print ("  vulkan - Use the Vulkan renderer\n");
 #else
@@ -567,11 +567,11 @@ get_renderer_for_backend (GdkSurface *surface)
 {
 #ifdef GDK_WINDOWING_X11
   if (GDK_IS_X11_SURFACE (surface))
-    return GSK_TYPE_NGL_RENDERER;
+    return GSK_TYPE_GL_RENDERER;
 #endif
 #ifdef GDK_WINDOWING_WAYLAND
   if (GDK_IS_WAYLAND_SURFACE (surface))
-    return GSK_TYPE_NGL_RENDERER;
+    return GSK_TYPE_GL_RENDERER;
 #endif
 #ifdef GDK_WINDOWING_BROADWAY
   if (GDK_IS_BROADWAY_SURFACE (surface))
@@ -579,7 +579,7 @@ get_renderer_for_backend (GdkSurface *surface)
 #endif
 #ifdef GDK_WINDOWING_MACOS
   if (GDK_IS_MACOS_SURFACE (surface))
-    return GSK_TYPE_NGL_RENDERER;
+    return GSK_TYPE_GL_RENDERER;
 #endif
 #ifdef GDK_WINDOWING_WIN32
   if (GDK_IS_WIN32_SURFACE (surface))
@@ -589,7 +589,7 @@ get_renderer_for_backend (GdkSurface *surface)
 
       if (!(GDK_DISPLAY_DEBUG_CHECK (display, GL_GLES) ||
             GDK_WIN32_DISPLAY (display)->running_on_arm64))
-        return GSK_TYPE_NGL_RENDERER;
+        return GSK_TYPE_GL_RENDERER;
     }
 #endif
 
index 4f984bf58c8fae0d7065cad10555666ba0df229e..b65b4261dbe19e79aea80270567bffbb163e136b 100644 (file)
@@ -1,24 +1,24 @@
-gsk_private_ngl_shaders = [
-  'ngl/resources/preamble.glsl',
-  'ngl/resources/preamble.fs.glsl',
-  'ngl/resources/preamble.vs.glsl',
-  'ngl/resources/border.glsl',
-  'ngl/resources/blit.glsl',
-  'ngl/resources/coloring.glsl',
-  'ngl/resources/color.glsl',
-  'ngl/resources/linear_gradient.glsl',
-  'ngl/resources/radial_gradient.glsl',
-  'ngl/resources/conic_gradient.glsl',
-  'ngl/resources/color_matrix.glsl',
-  'ngl/resources/blur.glsl',
-  'ngl/resources/inset_shadow.glsl',
-  'ngl/resources/outset_shadow.glsl',
-  'ngl/resources/unblurred_outset_shadow.glsl',
-  'ngl/resources/cross_fade.glsl',
-  'ngl/resources/blend.glsl',
-  'ngl/resources/repeat.glsl',
-  'ngl/resources/custom.glsl',
-  'ngl/resources/filled_border.glsl',
+gsk_private_gl_shaders = [
+  'gl/resources/preamble.glsl',
+  'gl/resources/preamble.fs.glsl',
+  'gl/resources/preamble.vs.glsl',
+  'gl/resources/border.glsl',
+  'gl/resources/blit.glsl',
+  'gl/resources/coloring.glsl',
+  'gl/resources/color.glsl',
+  'gl/resources/linear_gradient.glsl',
+  'gl/resources/radial_gradient.glsl',
+  'gl/resources/conic_gradient.glsl',
+  'gl/resources/color_matrix.glsl',
+  'gl/resources/blur.glsl',
+  'gl/resources/inset_shadow.glsl',
+  'gl/resources/outset_shadow.glsl',
+  'gl/resources/unblurred_outset_shadow.glsl',
+  'gl/resources/cross_fade.glsl',
+  'gl/resources/blend.glsl',
+  'gl/resources/repeat.glsl',
+  'gl/resources/custom.glsl',
+  'gl/resources/filled_border.glsl',
 ]
 
 gsk_public_sources = files([
@@ -31,7 +31,7 @@ gsk_public_sources = files([
   'gskrendernodeparser.c',
   'gskroundedrect.c',
   'gsktransform.c',
-  'ngl/gsknglrenderer.c',
+  'gl/gskglrenderer.c',
 ])
 
 gsk_private_sources = files([
@@ -39,26 +39,26 @@ gsk_private_sources = files([
   'gskdebug.c',
   'gskprivate.c',
   'gskprofiler.c',
-  'ngl/gsknglattachmentstate.c',
-  'ngl/gsknglbuffer.c',
-  'ngl/gsknglcommandqueue.c',
-  'ngl/gsknglcompiler.c',
-  'ngl/gskngldriver.c',
-  'ngl/gsknglglyphlibrary.c',
-  'ngl/gskngliconlibrary.c',
-  'ngl/gsknglprogram.c',
-  'ngl/gsknglrenderjob.c',
-  'ngl/gsknglshadowlibrary.c',
-  'ngl/gskngltexturelibrary.c',
-  'ngl/gskngluniformstate.c',
-  'ngl/gskngltexture.c',
-  'ngl/gskglprofiler.c',
-  'ngl/stb_rect_pack.c',
-  'ngl/fp16.c',
+  'gl/gskglattachmentstate.c',
+  'gl/gskglbuffer.c',
+  'gl/gskglcommandqueue.c',
+  'gl/gskglcompiler.c',
+  'gl/gskgldriver.c',
+  'gl/gskglglyphlibrary.c',
+  'gl/gskgliconlibrary.c',
+  'gl/gskglprogram.c',
+  'gl/gskglrenderjob.c',
+  'gl/gskglshadowlibrary.c',
+  'gl/gskgltexturelibrary.c',
+  'gl/gskgluniformstate.c',
+  'gl/gskgltexture.c',
+  'gl/gskglprofiler.c',
+  'gl/stb_rect_pack.c',
+  'gl/fp16.c',
 ])
 
 gsk_f16c_sources = files([
-  'ngl/fp16i.c',
+  'gl/fp16i.c',
 ])
 
 gsk_public_headers = files([
@@ -75,7 +75,7 @@ gsk_public_headers = files([
 install_headers(gsk_public_headers, 'gsk.h', subdir: 'gtk-4.0/gsk')
 
 gsk_public_gl_headers = files([
-  'ngl/gsknglrenderer.h',
+  'gl/gskglrenderer.h',
 ])
 install_headers(gsk_public_gl_headers, subdir: 'gtk-4.0/gsk/gl')
 gsk_public_headers += gsk_public_gl_headers
@@ -145,7 +145,7 @@ gsk_resources_xml = configure_file(output: 'gsk.resources.xml',
   command: [
     find_program('gen-gsk-gresources-xml.py'),
     '@OUTPUT@',
-    gsk_private_ngl_shaders,
+    gsk_private_gl_shaders,
     gsk_private_vulkan_compiled_shaders,
     gsk_private_vulkan_shaders
   ],
diff --git a/gsk/ngl/fp16.c b/gsk/ngl/fp16.c
deleted file mode 100644 (file)
index 29e8352..0000000
+++ /dev/null
@@ -1,254 +0,0 @@
-/* fp16.c
- *
- * Copyright 2021 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include "fp16private.h"
-
-static inline guint
-as_uint (const float x)
-{
-  return *(guint*)&x;
-}
-
-static inline float
-as_float (const guint x)
-{
-  return *(float*)&x;
-}
-
-// IEEE-754 16-bit floating-point format (without infinity): 1-5-10
-
-static inline float
-half_to_float_one (const guint16 x)
-{
-  const guint e = (x&0x7C00)>>10; // exponent
-  const guint m = (x&0x03FF)<<13; // mantissa
-  const guint v = as_uint((float)m)>>23;
-  return as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000)));
-}
-
-static inline guint16
-float_to_half_one (const float x)
-{
-  const guint b = as_uint(x)+0x00001000; // round-to-nearest-even
-  const guint e = (b&0x7F800000)>>23; // exponent
-  const guint m = b&0x007FFFFF; // mantissa
-  return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF; // sign : normalized : denormalized : saturate
-}
-
-void
-float_to_half4_c (const float f[4],
-                  guint16     h[4])
-{
-  h[0] = float_to_half_one (f[0]);
-  h[1] = float_to_half_one (f[1]);
-  h[2] = float_to_half_one (f[2]);
-  h[3] = float_to_half_one (f[3]);
-}
-
-void
-half_to_float4_c (const guint16 h[4],
-                  float         f[4])
-{
-  f[0] = half_to_float_one (h[0]);
-  f[1] = half_to_float_one (h[1]);
-  f[2] = half_to_float_one (h[2]);
-  f[3] = half_to_float_one (h[3]);
-}
-
-void
-float_to_half_c (const float *f,
-                 guint16     *h,
-                 int          n)
-{
-  for (int i = 0; i < n; i++)
-    h[i] = float_to_half_one (f[i]);
-}
-
-void
-half_to_float_c (const guint16 *h,
-                 float         *f,
-                 int            n)
-{
-  for (int i = 0; i < n; i++)
-    f[i] = half_to_float_one (h[i]);
-}
-
-#ifdef HAVE_F16C
-
-#if defined(_MSC_VER) && !defined(__clang__)
-/* based on info from https://walbourn.github.io/directxmath-f16c-and-fma/ */
-static gboolean
-have_f16c_msvc (void)
-{
-  static gboolean result = FALSE;
-  static gsize inited = 0;
-
-  if (g_once_init_enter (&inited))
-    {
-      int cpuinfo[4] = { -1 };
-
-      __cpuid (cpuinfo, 0);
-
-      if (cpuinfo[0] > 0)
-        {
-          __cpuid (cpuinfo, 1);
-
-          if ((cpuinfo[2] & 0x8000000) != 0)
-            result = (cpuinfo[2] & 0x20000000) != 0;
-        }
-
-      g_once_init_leave (&inited, 1);
-    }
-
-  return result;
-}
-
-void
-float_to_half4 (const float f[4], guint16 h[4])
-{
-  if (have_f16c_msvc ())
-    float_to_half4_f16c (f, h);
-  else
-    float_to_half4_c (f, h);
-}
-
-void
-half_to_float4 (const guint16 h[4], float f[4])
-{
-  if (have_f16c_msvc ())
-    half_to_float4_f16c (h, f);
-  else
-    half_to_float4_c (h, f);
-}
-
-void
-float_to_half (const float *f, guint16 *h, int n)
-{
-  if (have_f16c_msvc ())
-    float_to_half_f16c (f, h, n);
-  else
-    float_to_half_c (f, h, n);
-}
-
-void
-half_to_float (const guint16 *h, float *f, int n)
-{
-  if (have_f16c_msvc ())
-    half_to_float_f16c (h, f, n);
-  else
-    half_to_float_c (h, f, n);
-}
-
-#else
-
-void float_to_half4 (const float f[4], guint16 h[4]) __attribute__((ifunc ("resolve_float_to_half4")));
-void half_to_float4 (const guint16 h[4], float f[4]) __attribute__((ifunc ("resolve_half_to_float4")));
-void float_to_half (const float *f, guint16 *h, int n) __attribute__((ifunc ("resolve_float_to_half")));
-void half_to_float (const guint16 *h, float *f, int n) __attribute__((ifunc ("resolve_half_to_float")));
-
-static void *
-resolve_float_to_half4 (void)
-{
-  __builtin_cpu_init ();
-  if (__builtin_cpu_supports ("f16c"))
-    return float_to_half4_f16c;
-  else
-    return float_to_half4_c;
-}
-
-static void *
-resolve_half_to_float4 (void)
-{
-  __builtin_cpu_init ();
-  if (__builtin_cpu_supports ("f16c"))
-    return half_to_float4_f16c;
-  else
-    return half_to_float4_c;
-}
-
-static void *
-resolve_float_to_half (void)
-{
-  __builtin_cpu_init ();
-  if (__builtin_cpu_supports ("f16c"))
-    return float_to_half_f16c;
-  else
-    return float_to_half_c;
-}
-
-static void *
-resolve_half_to_float (void)
-{
-  __builtin_cpu_init ();
-  if (__builtin_cpu_supports ("f16c"))
-    return half_to_float_f16c;
-  else
-    return half_to_float_c;
-}
-
-#endif
-
-#else /* ! HAVE_F16C */
-
-#if defined(__APPLE__) || (defined(_MSC_VER) && !defined(__clang__))
-// turns out aliases don't work on Darwin nor Visual Studio
-
-void
-float_to_half4 (const float f[4],
-                guint16     h[4])
-{
-  float_to_half4_c (f, h);
-}
-
-void
-half_to_float4 (const guint16 h[4],
-                float         f[4])
-{
-  half_to_float4_c (h, f);
-}
-
-void
-float_to_half (const float *f,
-               guint16     *h,
-               int          n)
-{
-  float_to_half_c (f, h, n);
-}
-
-void
-half_to_float (const guint16 *h,
-               float         *f,
-               int            n)
-{
-  half_to_float_c (h, f, n);
-}
-
-#else
-
-void float_to_half4 (const float f[4], guint16 h[4]) __attribute__((alias ("float_to_half4_c")));
-void half_to_float4 (const guint16 h[4], float f[4]) __attribute__((alias ("half_to_float4_c")));
-void float_to_half (const float *f, guint16 *h, int n) __attribute__((alias ("float_to_half_c")));
-void half_to_float (const guint16 *h, float *f, int n) __attribute__((alias ("half_to_float_c")));
-
-#endif
-
-#endif  /* HAVE_F16C */
diff --git a/gsk/ngl/fp16i.c b/gsk/ngl/fp16i.c
deleted file mode 100644 (file)
index aff3841..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-/* fp16i.c
- *
- * Copyright 2021 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include "fp16private.h"
-
-#ifdef HAVE_F16C
-#include <immintrin.h>
-
-#if defined(_MSC_VER) && !defined(__clang__)
-#define CAST_M128I_P(a) (__m128i const *) a
-#else
-#define CAST_M128I_P(a) (__m128i_u const *) a
-#endif
-void
-float_to_half4_f16c (const float f[4],
-                     guint16     h[4])
-{
-  __m128 s = _mm_loadu_ps (f);
-  __m128i i = _mm_cvtps_ph (s, 0);
-  _mm_storel_epi64 ((__m128i*)h, i);
-}
-
-void
-half_to_float4_f16c (const guint16 h[4],
-                     float         f[4])
-{
-  __m128i i = _mm_loadl_epi64 (CAST_M128I_P (h));
-  __m128 s = _mm_cvtph_ps (i);
-
-  _mm_store_ps (f, s);
-}
-
-#define ALIGNED(p, n) (GPOINTER_TO_UINT(p) % n == 0)
-void
-float_to_half_f16c (const float *f,
-                    guint16     *h,
-                    int          n)
-{
-  __m128 s;
-  __m128i i;
-  int j;
-  const float *ff = f;
-  guint16 *hh = h;
-
-  for (j = 0; j < n; j++)
-    {
-      if (ALIGNED (ff, 16) && ALIGNED (hh, 16))
-        break;
-      ff++;
-      hh++;
-    }
-
-  float_to_half_c (f, h, j);
-
-  for (; j + 4 < n; j += 4)
-    {
-      s = _mm_loadu_ps (ff);
-      i = _mm_cvtps_ph (s, 0);
-      _mm_storel_epi64 ((__m128i*)hh, i);
-      ff += 4;
-      hh += 4;
-    }
-
-  if (j < n)
-    float_to_half_c (ff, hh, n - j);
-}
-
-void
-half_to_float_f16c (const guint16 *h,
-                    float         *f,
-                    int            n)
-{
-  __m128i i;
-  __m128 s;
-  int j;
-  const guint16 *hh = h;
-  float *ff = f;
-
-  for (j = 0; j < n; j++)
-    {
-      if (ALIGNED (ff, 16) && ALIGNED (hh, 16))
-        break;
-      ff++;
-      hh++;
-    }
-
-  half_to_float_c (h, f, j);
-
-  for (; j + 4 < n; j += 4)
-    {
-      i = _mm_loadl_epi64 (CAST_M128I_P (hh));
-      s = _mm_cvtph_ps (i);
-      _mm_store_ps (ff, s);
-      hh += 4;
-      ff += 4;
-    }
-
-  if (j < n)
-    half_to_float_c (hh, ff, n - j);
-}
-
-#endif  /* HAVE_F16C */
-
diff --git a/gsk/ngl/fp16private.h b/gsk/ngl/fp16private.h
deleted file mode 100644 (file)
index fbb95cd..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/* fp16private.h
- *
- * Copyright 2021 Red Hat, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __FP16_PRIVATE_H__
-#define __FP16_PRIVATE_H__
-
-#include <glib.h>
-
-G_BEGIN_DECLS
-
-#define FP16_ZERO ((guint16)0)
-#define FP16_ONE ((guint16)15360)
-#define FP16_MINUS_ONE ((guint16)48128)
-
-void float_to_half4 (const float f[4],
-                     guint16     h[4]);
-
-void half_to_float4 (const guint16 h[4],
-                     float         f[4]);
-
-void float_to_half (const float *f,
-                    guint16     *h,
-                    int          n);
-
-void half_to_float (const guint16 *h,
-                    float         *f,
-                    int            n);
-
-void float_to_half4_f16c (const float f[4],
-                          guint16     h[4]);
-
-void half_to_float4_f16c (const guint16 h[4],
-                          float         f[4]);
-
-void float_to_half_f16c (const float *f,
-                         guint16     *h,
-                         int          n);
-
-void half_to_float_f16c (const guint16 *h,
-                         float         *f,
-                         int            n);
-
-void float_to_half4_c (const float f[4],
-                       guint16     h[4]);
-
-void half_to_float4_c (const guint16 h[4],
-                       float         f[4]);
-
-void float_to_half_c (const float *f,
-                      guint16     *h,
-                      int          n);
-
-void half_to_float_c (const guint16 *h,
-                      float         *f,
-                      int            n);
-
-G_END_DECLS
-
-#endif
diff --git a/gsk/ngl/gskglprofiler.c b/gsk/ngl/gskglprofiler.c
deleted file mode 100644 (file)
index 9b834e5..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-#include "config.h"
-
-#include "gskglprofilerprivate.h"
-
-#include <epoxy/gl.h>
-
-#define N_QUERIES       4
-
-struct _GskGLProfiler
-{
-  GObject parent_instance;
-
-  GdkGLContext *gl_context;
-
-  /* Creating GL queries is kind of expensive, so we pay the
-   * price upfront and create a circular buffer of queries
-   */
-  GLuint gl_queries[N_QUERIES];
-  GLuint active_query;
-
-  gboolean has_queries : 1;
-  gboolean has_timer : 1;
-  gboolean first_frame : 1;
-};
-
-enum {
-  PROP_GL_CONTEXT = 1,
-
-  N_PROPERTIES
-};
-
-static GParamSpec *gsk_gl_profiler_properties[N_PROPERTIES];
-
-G_DEFINE_TYPE (GskGLProfiler, gsk_gl_profiler, G_TYPE_OBJECT)
-
-static void
-gsk_gl_profiler_finalize (GObject *gobject)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  if (self->has_queries)
-    glDeleteQueries (N_QUERIES, self->gl_queries);
-
-  g_clear_object (&self->gl_context);
-
-  G_OBJECT_CLASS (gsk_gl_profiler_parent_class)->finalize (gobject);
-}
-
-static void
-gsk_gl_profiler_set_property (GObject      *gobject,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      self->gl_context = g_value_dup_object (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_profiler_get_property (GObject    *gobject,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
-{
-  GskGLProfiler *self = GSK_GL_PROFILER (gobject);
-
-  switch (prop_id)
-    {
-    case PROP_GL_CONTEXT:
-      g_value_set_object (value, self->gl_context);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
-    }
-}
-
-static void
-gsk_gl_profiler_class_init (GskGLProfilerClass *klass)
-{
-  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-  gobject_class->set_property = gsk_gl_profiler_set_property;
-  gobject_class->get_property = gsk_gl_profiler_get_property;
-  gobject_class->finalize = gsk_gl_profiler_finalize;
-
-  gsk_gl_profiler_properties[PROP_GL_CONTEXT] =
-    g_param_spec_object ("gl-context",
-                         "GL Context",
-                         "The GdkGLContext used by the GL profiler",
-                         GDK_TYPE_GL_CONTEXT,
-                         G_PARAM_READWRITE |
-                         G_PARAM_CONSTRUCT_ONLY |
-                         G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gobject_class, N_PROPERTIES, gsk_gl_profiler_properties);
-}
-
-static void
-gsk_gl_profiler_init (GskGLProfiler *self)
-{
-  self->has_queries = epoxy_is_desktop_gl();
-  self->has_timer = epoxy_is_desktop_gl() && (epoxy_gl_version () >= 33 || epoxy_has_gl_extension ("GL_ARB_timer_query"));
-
-  if (!self->has_queries)
-    return;
-
-  glGenQueries (N_QUERIES, self->gl_queries);
-  self->first_frame = TRUE;
-}
-
-GskGLProfiler *
-gsk_gl_profiler_new (GdkGLContext *context)
-{
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
-
-  return g_object_new (GSK_TYPE_GL_PROFILER, "gl-context", context, NULL);
-}
-
-void
-gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler)
-{
-  GLuint query_id;
-
-  g_return_if_fail (GSK_IS_GL_PROFILER (profiler));
-
-  if (!profiler->has_timer || !profiler->has_queries)
-    return;
-
-  query_id = profiler->gl_queries[profiler->active_query];
-  glBeginQuery (GL_TIME_ELAPSED, query_id);
-}
-
-guint64
-gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler)
-{
-  GLuint last_query_id;
-  GLint res;
-  GLuint64 elapsed;
-
-  g_return_val_if_fail (GSK_IS_GL_PROFILER (profiler), 0);
-
-  if (!profiler->has_timer || !profiler->has_queries)
-    return 0;
-
-  glEndQuery (GL_TIME_ELAPSED);
-
-  if (profiler->active_query == 0)
-    last_query_id = N_QUERIES - 1;
-  else
-    last_query_id = profiler->active_query - 1;
-
-  /* Advance iterator */
-  profiler->active_query += 1;
-  if (profiler->active_query == N_QUERIES)
-    profiler->active_query = 0;
-
-  /* If this is the first frame we already have a result */
-  if (profiler->first_frame)
-    {
-      profiler->first_frame = FALSE;
-      return 0;
-    }
-
-  glGetQueryObjectiv (profiler->gl_queries[last_query_id], GL_QUERY_RESULT_AVAILABLE, &res);
-  if (res == 1)
-    glGetQueryObjectui64v (profiler->gl_queries[last_query_id], GL_QUERY_RESULT, &elapsed);
-  else
-    elapsed = 0;
-
-  return elapsed / 1000; /* Convert to usec to match other profiler APIs */
-}
diff --git a/gsk/ngl/gskglprofilerprivate.h b/gsk/ngl/gskglprofilerprivate.h
deleted file mode 100644 (file)
index 5b2a24b..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef __GSK_GL_PROFILER_PRIVATE_H__
-#define __GSK_GL_PROFILER_PRIVATE_H__
-
-#include <gsk/gsktypes.h>
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_PROFILER (gsk_gl_profiler_get_type ())
-G_DECLARE_FINAL_TYPE (GskGLProfiler, gsk_gl_profiler, GSK, GL_PROFILER, GObject)
-
-GskGLProfiler * gsk_gl_profiler_new                     (GdkGLContext  *context);
-
-void            gsk_gl_profiler_begin_gpu_region        (GskGLProfiler *profiler);
-guint64         gsk_gl_profiler_end_gpu_region          (GskGLProfiler *profiler);
-
-G_END_DECLS
-
-#endif /* __GSK_GL_PROFILER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglattachmentstate.c b/gsk/ngl/gsknglattachmentstate.c
deleted file mode 100644 (file)
index bf37087..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/* gsknglattachmentstate.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include "gsknglattachmentstateprivate.h"
-
-GskNglAttachmentState *
-gsk_ngl_attachment_state_new (void)
-{
-  GskNglAttachmentState *self;
-
-  self = g_atomic_rc_box_new0 (GskNglAttachmentState);
-
-  self->fbo.changed = FALSE;
-  self->fbo.id = 0;
-  self->n_changed = 0;
-
-  /* Initialize textures, assume we are 2D by default since it
-   * doesn't really matter until we bind something other than
-   * GL_TEXTURE0 to it anyway.
-   */
-  for (guint i = 0; i < G_N_ELEMENTS (self->textures); i++)
-    {
-      self->textures[i].target = GL_TEXTURE_2D;
-      self->textures[i].texture = GL_TEXTURE0;
-      self->textures[i].id = 0;
-      self->textures[i].changed = FALSE;
-      self->textures[i].initial = TRUE;
-    }
-
-  return self;
-}
-
-GskNglAttachmentState *
-gsk_ngl_attachment_state_ref (GskNglAttachmentState *self)
-{
-  return g_atomic_rc_box_acquire (self);
-}
-
-void
-gsk_ngl_attachment_state_unref (GskNglAttachmentState *self)
-{
-  g_atomic_rc_box_release (self);
-}
-
-void
-gsk_ngl_attachment_state_bind_texture (GskNglAttachmentState *self,
-                                       GLenum                 target,
-                                       GLenum                 texture,
-                                       guint                  id)
-{
-  GskNglBindTexture *attach;
-
-  g_assert (self != NULL);
-  g_assert (target == GL_TEXTURE_1D ||
-            target == GL_TEXTURE_2D ||
-            target == GL_TEXTURE_3D);
-  g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16);
-
-  attach = &self->textures[texture - GL_TEXTURE0];
-
-  if (attach->target != target || attach->texture != texture || attach->id != id)
-    {
-      attach->target = target;
-      attach->texture = texture;
-      attach->id = id;
-      attach->initial = FALSE;
-
-      if (attach->changed == FALSE)
-        {
-          attach->changed = TRUE;
-          self->n_changed++;
-        }
-    }
-}
-
-void
-gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
-                                           guint                  id)
-{
-  g_assert (self != NULL);
-
-  if (self->fbo.id != id)
-    {
-      self->fbo.id = id;
-      self->fbo.changed = TRUE;
-    }
-}
diff --git a/gsk/ngl/gsknglattachmentstateprivate.h b/gsk/ngl/gsknglattachmentstateprivate.h
deleted file mode 100644 (file)
index b43e91e..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/* gsknglattachmentstateprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
-#define __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-
-G_BEGIN_DECLS
-
-typedef struct _GskNglAttachmentState GskNglAttachmentState;
-typedef struct _GskNglBindFramebuffer GskNglBindFramebuffer;
-typedef struct _GskNglBindTexture     GskNglBindTexture;
-
-struct _GskNglBindTexture
-{
-  guint changed : 1;
-  guint initial : 1;
-  GLenum target : 30;
-  GLenum texture;
-  guint id;
-};
-
-G_STATIC_ASSERT (sizeof (GskNglBindTexture) == 12);
-
-struct _GskNglBindFramebuffer
-{
-  guint changed : 1;
-  guint id : 31;
-};
-
-G_STATIC_ASSERT (sizeof (GskNglBindFramebuffer) == 4);
-
-struct _GskNglAttachmentState
-{
-  GskNglBindFramebuffer fbo;
-  /* Increase if shaders add more textures */
-  GskNglBindTexture textures[4];
-  guint n_changed;
-};
-
-GskNglAttachmentState *gsk_ngl_attachment_state_new              (void);
-GskNglAttachmentState *gsk_ngl_attachment_state_ref              (GskNglAttachmentState *self);
-void                   gsk_ngl_attachment_state_unref            (GskNglAttachmentState *self);
-void                   gsk_ngl_attachment_state_bind_texture     (GskNglAttachmentState *self,
-                                                                  GLenum                 target,
-                                                                  GLenum                 texture,
-                                                                  guint                  id);
-void                   gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
-                                                                  guint                  id);
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglbuffer.c b/gsk/ngl/gsknglbuffer.c
deleted file mode 100644 (file)
index 6855f1c..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/* gsknglbufferprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <string.h>
-
-#include "gsknglbufferprivate.h"
-
-/**
- * gsk_ngl_buffer_init:
- * @target: the target buffer such as %GL_ARRAY_BUFFER or %GL_UNIFORM_BUFFER
- * @element_size: the size of elements within the buffer
- *
- * Creates a new `GskNglBuffer` which can be used to deliver data to shaders
- * within a GLSL program. You can use this to store vertices such as with
- * %GL_ARRAY_BUFFER or uniform data with %GL_UNIFORM_BUFFER.
- */
-void
-gsk_ngl_buffer_init (GskNglBuffer *self,
-                     GLenum        target,
-                     guint         element_size)
-{
-  memset (self, 0, sizeof *self);
-
-  /* Default to 2 pages, power-of-two growth from there */
-  self->buffer_len = 4096 * 2;
-  self->buffer = g_malloc (self->buffer_len);
-  self->target = target;
-  self->element_size = element_size;
-}
-
-GLuint
-gsk_ngl_buffer_submit (GskNglBuffer *buffer)
-{
-  GLuint id;
-
-  glGenBuffers (1, &id);
-  glBindBuffer (buffer->target, id);
-  glBufferData (buffer->target, buffer->buffer_pos, buffer->buffer, GL_STATIC_DRAW);
-
-  buffer->buffer_pos = 0;
-  buffer->count = 0;
-
-  return id;
-}
-
-void
-gsk_ngl_buffer_destroy (GskNglBuffer *buffer)
-{
-  g_clear_pointer (&buffer->buffer, g_free);
-}
diff --git a/gsk/ngl/gsknglbufferprivate.h b/gsk/ngl/gsknglbufferprivate.h
deleted file mode 100644 (file)
index cbd21c8..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/* gsknglbufferprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_BUFFER_PRIVATE_H__
-#define __GSK_NGL_BUFFER_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-
-G_BEGIN_DECLS
-
-typedef struct _GskNglBuffer
-{
-  guint8 *buffer;
-  gsize   buffer_pos;
-  gsize   buffer_len;
-  guint   count;
-  GLenum  target;
-  gsize   element_size;
-} GskNglBuffer;
-
-void   gsk_ngl_buffer_init    (GskNglBuffer *self,
-                               GLenum        target,
-                               guint         element_size);
-void   gsk_ngl_buffer_destroy (GskNglBuffer *buffer);
-GLuint gsk_ngl_buffer_submit  (GskNglBuffer *buffer);
-
-static inline gpointer
-gsk_ngl_buffer_advance (GskNglBuffer *buffer,
-                        guint         count)
-{
-  gpointer ret;
-  gsize to_alloc = count * buffer->element_size;
-
-  if G_UNLIKELY (buffer->buffer_pos + to_alloc > buffer->buffer_len)
-    {
-      while (buffer->buffer_pos + to_alloc > buffer->buffer_len)
-        buffer->buffer_len *= 2;
-      buffer->buffer = g_realloc (buffer->buffer, buffer->buffer_len);
-    }
-
-  ret = buffer->buffer + buffer->buffer_pos;
-
-  buffer->buffer_pos += to_alloc;
-  buffer->count += count;
-
-  return ret;
-}
-
-static inline void
-gsk_ngl_buffer_retract (GskNglBuffer *buffer,
-                        guint         count)
-{
-  buffer->buffer_pos -= count * buffer->element_size;
-  buffer->count -= count;
-}
-
-static inline guint
-gsk_ngl_buffer_get_offset (GskNglBuffer *buffer)
-{
-  return buffer->count;
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_BUFFER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglcommandqueue.c b/gsk/ngl/gsknglcommandqueue.c
deleted file mode 100644 (file)
index 8dde9ff..0000000
+++ /dev/null
@@ -1,1433 +0,0 @@
-/* gsknglcommandqueue.c
- *
- * Copyright 2017 Timm Bäder <mail@baedert.org>
- * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
- * Copyright 2018 Alexander Larsson <alexl@redhat.com>
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <string.h>
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdkmemoryformatprivate.h>
-#include <gdk/gdkmemorytextureprivate.h>
-#include <gdk/gdkprofilerprivate.h>
-#include <gsk/gskdebugprivate.h>
-#include <gsk/gskroundedrectprivate.h>
-
-#include "gsknglattachmentstateprivate.h"
-#include "gsknglbufferprivate.h"
-#include "gsknglcommandqueueprivate.h"
-#include "gskngluniformstateprivate.h"
-
-#include "inlinearray.h"
-
-G_DEFINE_TYPE (GskNglCommandQueue, gsk_ngl_command_queue, G_TYPE_OBJECT)
-
-G_GNUC_UNUSED static inline void
-print_uniform (GskNglUniformFormat format,
-               guint               array_count,
-               gconstpointer       valueptr)
-{
-  const union {
-    graphene_matrix_t matrix[0];
-    GskRoundedRect rounded_rect[0];
-    float fval[0];
-    int ival[0];
-    guint uval[0];
-  } *data = valueptr;
-
-  switch (format)
-    {
-    case GSK_NGL_UNIFORM_FORMAT_1F:
-      g_printerr ("1f<%f>", data->fval[0]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_2F:
-      g_printerr ("2f<%f,%f>", data->fval[0], data->fval[1]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_3F:
-      g_printerr ("3f<%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_4F:
-      g_printerr ("4f<%f,%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2], data->fval[3]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_1I:
-    case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
-      g_printerr ("1i<%d>", data->ival[0]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_1UI:
-      g_printerr ("1ui<%u>", data->uval[0]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_COLOR: {
-      char *str = gdk_rgba_to_string (valueptr);
-      g_printerr ("%s", str);
-      g_free (str);
-      break;
-    }
-
-    case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT: {
-      char *str = gsk_rounded_rect_to_string (valueptr);
-      g_printerr ("%s", str);
-      g_free (str);
-      break;
-    }
-
-    case GSK_NGL_UNIFORM_FORMAT_MATRIX: {
-      float mat[16];
-      graphene_matrix_to_float (&data->matrix[0], mat);
-      g_printerr ("matrix<");
-      for (guint i = 0; i < G_N_ELEMENTS (mat)-1; i++)
-        g_printerr ("%f,", mat[i]);
-      g_printerr ("%f>", mat[G_N_ELEMENTS (mat)-1]);
-      break;
-    }
-
-    case GSK_NGL_UNIFORM_FORMAT_1FV:
-    case GSK_NGL_UNIFORM_FORMAT_2FV:
-    case GSK_NGL_UNIFORM_FORMAT_3FV:
-    case GSK_NGL_UNIFORM_FORMAT_4FV:
-      /* non-V variants are -4 from V variants */
-      format -= 4;
-      g_printerr ("[");
-      for (guint i = 0; i < array_count; i++)
-        {
-          print_uniform (format, 0, valueptr);
-          if (i + 1 != array_count)
-            g_printerr (",");
-          valueptr = ((guint8*)valueptr + gsk_ngl_uniform_format_size (format));
-        }
-      g_printerr ("]");
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_2I:
-      g_printerr ("2i<%d,%d>", data->ival[0], data->ival[1]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_3I:
-      g_printerr ("3i<%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_4I:
-      g_printerr ("3i<%d,%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2], data->ival[3]);
-      break;
-
-    case GSK_NGL_UNIFORM_FORMAT_LAST:
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-G_GNUC_UNUSED static inline void
-gsk_ngl_command_queue_print_batch (GskNglCommandQueue       *self,
-                                   const GskNglCommandBatch *batch)
-{
-  static const char *command_kinds[] = { "Clear", "Draw", };
-  guint framebuffer_id;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (batch != NULL);
-
-  if (batch->any.kind == GSK_NGL_COMMAND_KIND_CLEAR)
-    framebuffer_id = batch->clear.framebuffer;
-  else if (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW)
-    framebuffer_id = batch->draw.framebuffer;
-  else
-    return;
-
-  g_printerr ("Batch {\n");
-  g_printerr ("         Kind: %s\n", command_kinds[batch->any.kind]);
-  g_printerr ("     Viewport: %dx%d\n", batch->any.viewport.width, batch->any.viewport.height);
-  g_printerr ("  Framebuffer: %d\n", framebuffer_id);
-
-  if (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW)
-    {
-      g_printerr ("      Program: %d\n", batch->any.program);
-      g_printerr ("     Vertices: %d\n", batch->draw.vbo_count);
-
-      for (guint i = 0; i < batch->draw.bind_count; i++)
-        {
-          const GskNglCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i];
-          g_printerr ("      Bind[%d]: %u\n", bind->texture, bind->id);
-        }
-
-      for (guint i = 0; i < batch->draw.uniform_count; i++)
-        {
-          const GskNglCommandUniform *uniform = &self->batch_uniforms.items[batch->draw.uniform_offset + i];
-          g_printerr ("  Uniform[%02d]: ", uniform->location);
-          print_uniform (uniform->info.format,
-                         uniform->info.array_count,
-                         gsk_ngl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset));
-          g_printerr ("\n");
-        }
-    }
-  else if (batch->any.kind == GSK_NGL_COMMAND_KIND_CLEAR)
-    {
-      g_printerr ("         Bits: 0x%x\n", batch->clear.bits);
-    }
-
-  g_printerr ("}\n");
-}
-
-G_GNUC_UNUSED static inline void
-gsk_ngl_command_queue_capture_png (GskNglCommandQueue *self,
-                                   const char         *filename,
-                                   guint               width,
-                                   guint               height,
-                                   gboolean            flip_y)
-{
-  guint stride;
-  guint8 *data;
-  GBytes *bytes;
-  GdkTexture *texture;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (filename != NULL);
-
-  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
-  data = g_malloc_n (height, stride);
-
-  glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, data);
-
-  if (flip_y)
-    {
-      guint8 *flipped = g_malloc_n (height, stride);
-
-      for (guint i = 0; i < height; i++)
-        memcpy (flipped + (height * stride) - ((i + 1) * stride),
-                data + (stride * i),
-                stride);
-
-      g_free (data);
-      data = flipped;
-    }
-
-  bytes = g_bytes_new_take (data, height * stride);
-  texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride);
-  g_bytes_unref (bytes);
-
-  gdk_texture_save_to_png (texture, filename);
-  g_object_unref (texture);
-}
-
-static inline gboolean
-will_ignore_batch (GskNglCommandQueue *self)
-{
-  if G_LIKELY (self->batches.len < G_MAXINT16)
-    return FALSE;
-
-  if (!self->have_truncated)
-    {
-      self->have_truncated = TRUE;
-      g_critical ("GL command queue too large, truncating further batches.");
-    }
-
-  return TRUE;
-}
-
-static inline guint
-snapshot_attachments (const GskNglAttachmentState *state,
-                      GskNglCommandBinds          *array)
-{
-  GskNglCommandBind *bind = gsk_ngl_command_binds_append_n (array, G_N_ELEMENTS (state->textures));
-  guint count = 0;
-
-  for (guint i = 0; i < G_N_ELEMENTS (state->textures); i++)
-    {
-      if (state->textures[i].id)
-        {
-          bind[count].id = state->textures[i].id;
-          bind[count].texture = state->textures[i].texture;
-          count++;
-        }
-    }
-
-  if (count != G_N_ELEMENTS (state->textures))
-    array->len -= G_N_ELEMENTS (state->textures) - count;
-
-  return count;
-}
-
-static inline guint
-snapshot_uniforms (GskNglUniformState    *state,
-                   GskNglUniformProgram  *program,
-                   GskNglCommandUniforms *array)
-{
-  GskNglCommandUniform *uniform = gsk_ngl_command_uniforms_append_n (array, program->n_mappings);
-  guint count = 0;
-
-  for (guint i = 0; i < program->n_mappings; i++)
-    {
-      const GskNglUniformMapping *mapping = &program->mappings[i];
-
-      if (!mapping->info.initial && mapping->location > -1)
-        {
-          uniform[count].location = mapping->location;
-          uniform[count].info = mapping->info;
-          count++;
-        }
-    }
-
-  if (count != program->n_mappings)
-    array->len -= program->n_mappings - count;
-
-  return count;
-}
-
-static inline gboolean
-snapshots_equal (GskNglCommandQueue *self,
-                 GskNglCommandBatch *first,
-                 GskNglCommandBatch *second)
-{
-  if (first->draw.bind_count != second->draw.bind_count ||
-      first->draw.uniform_count != second->draw.uniform_count)
-    return FALSE;
-
-  for (guint i = 0; i < first->draw.bind_count; i++)
-    {
-      const GskNglCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i];
-      const GskNglCommandBind *sb = &self->batch_binds.items[second->draw.bind_offset+i];
-
-      if (fb->id != sb->id || fb->texture != sb->texture)
-        return FALSE;
-    }
-
-  for (guint i = 0; i < first->draw.uniform_count; i++)
-    {
-      const GskNglCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i];
-      const GskNglCommandUniform *su = &self->batch_uniforms.items[second->draw.uniform_offset+i];
-      gconstpointer fdata;
-      gconstpointer sdata;
-      gsize len;
-
-      /* Short circuit if we'd end up with the same memory */
-      if (fu->info.offset == su->info.offset)
-        continue;
-
-      if (fu->info.format != su->info.format ||
-          fu->info.array_count != su->info.array_count)
-        return FALSE;
-
-      fdata = gsk_ngl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset);
-      sdata = gsk_ngl_uniform_state_get_uniform_data (self->uniforms, su->info.offset);
-
-      switch (fu->info.format)
-        {
-        case GSK_NGL_UNIFORM_FORMAT_1F:
-        case GSK_NGL_UNIFORM_FORMAT_1FV:
-        case GSK_NGL_UNIFORM_FORMAT_1I:
-        case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
-        case GSK_NGL_UNIFORM_FORMAT_1UI:
-          len = 4;
-          break;
-
-        case GSK_NGL_UNIFORM_FORMAT_2F:
-        case GSK_NGL_UNIFORM_FORMAT_2FV:
-        case GSK_NGL_UNIFORM_FORMAT_2I:
-          len = 8;
-          break;
-
-        case GSK_NGL_UNIFORM_FORMAT_3F:
-        case GSK_NGL_UNIFORM_FORMAT_3FV:
-        case GSK_NGL_UNIFORM_FORMAT_3I:
-          len = 12;
-          break;
-
-        case GSK_NGL_UNIFORM_FORMAT_4F:
-        case GSK_NGL_UNIFORM_FORMAT_4FV:
-        case GSK_NGL_UNIFORM_FORMAT_4I:
-          len = 16;
-          break;
-
-
-        case GSK_NGL_UNIFORM_FORMAT_MATRIX:
-          len = sizeof (float) * 16;
-          break;
-
-        case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT:
-          len = sizeof (float) * 12;
-          break;
-
-        case GSK_NGL_UNIFORM_FORMAT_COLOR:
-          len = sizeof (float) * 4;
-          break;
-
-        default:
-          g_assert_not_reached ();
-        }
-
-      len *= fu->info.array_count;
-
-      if (memcmp (fdata, sdata, len) != 0)
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-static void
-gsk_ngl_command_queue_dispose (GObject *object)
-{
-  GskNglCommandQueue *self = (GskNglCommandQueue *)object;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  g_clear_object (&self->profiler);
-  g_clear_object (&self->gl_profiler);
-  g_clear_object (&self->context);
-  g_clear_pointer (&self->attachments, gsk_ngl_attachment_state_unref);
-  g_clear_pointer (&self->uniforms, gsk_ngl_uniform_state_unref);
-
-  gsk_ngl_command_batches_clear (&self->batches);
-  gsk_ngl_command_binds_clear (&self->batch_binds);
-  gsk_ngl_command_uniforms_clear (&self->batch_uniforms);
-
-  gsk_ngl_buffer_destroy (&self->vertices);
-
-  G_OBJECT_CLASS (gsk_ngl_command_queue_parent_class)->dispose (object);
-}
-
-static void
-gsk_ngl_command_queue_class_init (GskNglCommandQueueClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->dispose = gsk_ngl_command_queue_dispose;
-}
-
-static void
-gsk_ngl_command_queue_init (GskNglCommandQueue *self)
-{
-  self->max_texture_size = -1;
-
-  gsk_ngl_command_batches_init (&self->batches, 128);
-  gsk_ngl_command_binds_init (&self->batch_binds, 1024);
-  gsk_ngl_command_uniforms_init (&self->batch_uniforms, 2048);
-
-  gsk_ngl_buffer_init (&self->vertices, GL_ARRAY_BUFFER, sizeof (GskNglDrawVertex));
-}
-
-GskNglCommandQueue *
-gsk_ngl_command_queue_new (GdkGLContext       *context,
-                           GskNglUniformState *uniforms)
-{
-  GskNglCommandQueue *self;
-
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
-
-  self = g_object_new (GSK_TYPE_GL_COMMAND_QUEUE, NULL);
-  self->context = g_object_ref (context);
-  self->attachments = gsk_ngl_attachment_state_new ();
-
-  /* Use shared uniform state if we're provided one */
-  if (uniforms != NULL)
-    self->uniforms = gsk_ngl_uniform_state_ref (uniforms);
-  else
-    self->uniforms = gsk_ngl_uniform_state_new ();
-
-  /* Determine max texture size immediately and restore context */
-  gdk_gl_context_make_current (context);
-  glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
-
-  return g_steal_pointer (&self);
-}
-
-static inline GskNglCommandBatch *
-begin_next_batch (GskNglCommandQueue *self)
-{
-  GskNglCommandBatch *batch;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  /* GskNglCommandBatch contains an embedded linked list using integers into the
-   * self->batches array. We can't use pointer because the batches could be
-   * realloc()'d at runtime.
-   *
-   * Before we execute the command queue, we sort the batches by framebuffer but
-   * leave the batches in place as we can just tweak the links via prev/next.
-   *
-   * Generally we only traverse forwards, so we could ignore the previous field.
-   * But to optimize the reordering of batches by framebuffer we walk backwards
-   * so we sort by most-recently-seen framebuffer to ensure draws happen in the
-   * proper order.
-   */
-
-  batch = gsk_ngl_command_batches_append (&self->batches);
-  batch->any.next_batch_index = -1;
-  batch->any.prev_batch_index = self->tail_batch_index;
-
-  return batch;
-}
-
-static void
-enqueue_batch (GskNglCommandQueue *self)
-{
-  guint index;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->batches.len > 0);
-
-  /* Batches are linked lists but using indexes into the batches array instead
-   * of pointers. This is for two main reasons. First, 16-bit indexes allow us
-   * to store the information in 4 bytes, where as two pointers would take 16
-   * bytes.  Furthermore, we have an array here so pointers would get
-   * invalidated if we realloc()'d (and that can happen from time to time).
-   */
-
-  index = self->batches.len - 1;
-
-  if (self->head_batch_index == -1)
-    self->head_batch_index = index;
-
-  if (self->tail_batch_index != -1)
-    {
-      GskNglCommandBatch *prev = &self->batches.items[self->tail_batch_index];
-
-      prev->any.next_batch_index = index;
-    }
-
-  self->tail_batch_index = index;
-}
-
-static void
-discard_batch (GskNglCommandQueue *self)
-{
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->batches.len > 0);
-
-  self->batches.len--;
-}
-
-void
-gsk_ngl_command_queue_begin_draw (GskNglCommandQueue   *self,
-                                  GskNglUniformProgram *program,
-                                  guint                 width,
-                                  guint                 height)
-{
-  GskNglCommandBatch *batch;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->in_draw == FALSE);
-  g_assert (width <= G_MAXUINT16);
-  g_assert (height <= G_MAXUINT16);
-
-  /* Our internal links use 16-bits, so that is our max number
-   * of batches we can have in one frame.
-   */
-  if (will_ignore_batch (self))
-    return;
-
-  self->program_info = program;
-
-  batch = begin_next_batch (self);
-  batch->any.kind = GSK_NGL_COMMAND_KIND_DRAW;
-  batch->any.program = program->program_id;
-  batch->any.next_batch_index = -1;
-  batch->any.viewport.width = width;
-  batch->any.viewport.height = height;
-  batch->draw.framebuffer = 0;
-  batch->draw.uniform_count = 0;
-  batch->draw.uniform_offset = self->batch_uniforms.len;
-  batch->draw.bind_count = 0;
-  batch->draw.bind_offset = self->batch_binds.len;
-  batch->draw.vbo_count = 0;
-  batch->draw.vbo_offset = gsk_ngl_buffer_get_offset (&self->vertices);
-
-  self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer);
-
-  self->in_draw = TRUE;
-}
-
-void
-gsk_ngl_command_queue_end_draw (GskNglCommandQueue *self)
-{
-  GskNglCommandBatch *last_batch;
-  GskNglCommandBatch *batch;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->batches.len > 0);
-
-  if (will_ignore_batch (self))
-    return;
-
-  batch = gsk_ngl_command_batches_tail (&self->batches);
-
-  g_assert (self->in_draw == TRUE);
-  g_assert (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW);
-
-  if G_UNLIKELY (batch->draw.vbo_count == 0)
-    {
-      discard_batch (self);
-      self->in_draw = FALSE;
-      return;
-    }
-
-  /* Track the destination framebuffer in case it changed */
-  batch->draw.framebuffer = self->attachments->fbo.id;
-  self->attachments->fbo.changed = FALSE;
-  self->fbo_max = MAX (self->fbo_max, self->attachments->fbo.id);
-
-  /* Save our full uniform state for this draw so we can possibly
-   * reorder the draw later.
-   */
-  batch->draw.uniform_offset = self->batch_uniforms.len;
-  batch->draw.uniform_count = snapshot_uniforms (self->uniforms, self->program_info, &self->batch_uniforms);
-
-  /* Track the bind attachments that changed */
-  if (self->program_info->has_attachments)
-    {
-      batch->draw.bind_offset = self->batch_binds.len;
-      batch->draw.bind_count = snapshot_attachments (self->attachments, &self->batch_binds);
-    }
-  else
-    {
-      batch->draw.bind_offset = 0;
-      batch->draw.bind_count = 0;
-    }
-
-  if (self->batches.len > 1)
-    last_batch = &self->batches.items[self->batches.len - 2];
-  else
-    last_batch = NULL;
-
-  /* Do simple chaining of draw to last batch. */
-  if (last_batch != NULL &&
-      last_batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW &&
-      last_batch->any.program == batch->any.program &&
-      last_batch->any.viewport.width == batch->any.viewport.width &&
-      last_batch->any.viewport.height == batch->any.viewport.height &&
-      last_batch->draw.framebuffer == batch->draw.framebuffer &&
-      last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset &&
-      last_batch->draw.vbo_count + batch->draw.vbo_count <= 0xffff &&
-      snapshots_equal (self, last_batch, batch))
-    {
-      last_batch->draw.vbo_count += batch->draw.vbo_count;
-      discard_batch (self);
-    }
-  else
-    {
-      enqueue_batch (self);
-    }
-
-  self->in_draw = FALSE;
-  self->program_info = NULL;
-}
-
-/**
- * gsk_ngl_command_queue_split_draw:
- * @self a `GskNglCommandQueue`
- *
- * This function is like calling gsk_ngl_command_queue_end_draw() followed by
- * a gsk_ngl_command_queue_begin_draw() with the same parameters as a
- * previous begin draw (if shared uniforms where not changed further).
- *
- * This is useful to avoid comparisons inside of loops where we know shared
- * uniforms are not changing.
- *
- * This generally should just be called from gsk_ngl_program_split_draw()
- * as that is where the begin/end flow happens from the render job.
- */
-void
-gsk_ngl_command_queue_split_draw (GskNglCommandQueue *self)
-{
-  GskNglCommandBatch *batch;
-  GskNglUniformProgram *program;
-  guint width;
-  guint height;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->batches.len > 0);
-  g_assert (self->in_draw == TRUE);
-
-  program = self->program_info;
-
-  batch = gsk_ngl_command_batches_tail (&self->batches);
-
-  g_assert (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW);
-
-  width = batch->any.viewport.width;
-  height = batch->any.viewport.height;
-
-  gsk_ngl_command_queue_end_draw (self);
-  gsk_ngl_command_queue_begin_draw (self, program, width, height);
-}
-
-void
-gsk_ngl_command_queue_clear (GskNglCommandQueue    *self,
-                             guint                  clear_bits,
-                             const graphene_rect_t *viewport)
-{
-  GskNglCommandBatch *batch;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->in_draw == FALSE);
-
-  if (will_ignore_batch (self))
-    return;
-
-  if (clear_bits == 0)
-    clear_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
-
-  batch = begin_next_batch (self);
-  batch->any.kind = GSK_NGL_COMMAND_KIND_CLEAR;
-  batch->any.viewport.width = viewport->size.width;
-  batch->any.viewport.height = viewport->size.height;
-  batch->clear.bits = clear_bits;
-  batch->clear.framebuffer = self->attachments->fbo.id;
-  batch->any.next_batch_index = -1;
-  batch->any.program = 0;
-
-  self->fbo_max = MAX (self->fbo_max, batch->clear.framebuffer);
-
-  enqueue_batch (self);
-
-  self->attachments->fbo.changed = FALSE;
-}
-
-GdkGLContext *
-gsk_ngl_command_queue_get_context (GskNglCommandQueue *self)
-{
-  g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self), NULL);
-
-  return self->context;
-}
-
-void
-gsk_ngl_command_queue_make_current (GskNglCommandQueue *self)
-{
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (GDK_IS_GL_CONTEXT (self->context));
-
-  gdk_gl_context_make_current (self->context);
-}
-
-void
-gsk_ngl_command_queue_delete_program (GskNglCommandQueue *self,
-                                      guint               program)
-{
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  glDeleteProgram (program);
-}
-
-static inline void
-apply_viewport (guint *current_width,
-                guint *current_height,
-                guint  width,
-                guint  height)
-{
-  if G_UNLIKELY (*current_width != width || *current_height != height)
-    {
-      *current_width = width;
-      *current_height = height;
-      glViewport (0, 0, width, height);
-    }
-}
-
-static inline void
-apply_scissor (gboolean              *state,
-               guint                  framebuffer,
-               const graphene_rect_t *scissor,
-               gboolean               has_scissor)
-{
-  g_assert (framebuffer != (guint)-1);
-
-  if (framebuffer != 0 || !has_scissor)
-    {
-      if (*state != FALSE)
-        {
-          glDisable (GL_SCISSOR_TEST);
-          *state = FALSE;
-        }
-    }
-  else
-    {
-      if (*state != TRUE)
-        {
-          glEnable (GL_SCISSOR_TEST);
-          glScissor (scissor->origin.x,
-                     scissor->origin.y,
-                     scissor->size.width,
-                     scissor->size.height);
-          *state = TRUE;
-        }
-    }
-}
-
-static inline gboolean
-apply_framebuffer (int   *framebuffer,
-                   guint  new_framebuffer)
-{
-  if G_UNLIKELY (new_framebuffer != *framebuffer)
-    {
-      *framebuffer = new_framebuffer;
-      glBindFramebuffer (GL_FRAMEBUFFER, new_framebuffer);
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static inline void
-gsk_ngl_command_queue_unlink (GskNglCommandQueue *self,
-                              GskNglCommandBatch *batch)
-{
-  if (batch->any.prev_batch_index == -1)
-    self->head_batch_index = batch->any.next_batch_index;
-  else
-    self->batches.items[batch->any.prev_batch_index].any.next_batch_index = batch->any.next_batch_index;
-
-  if (batch->any.next_batch_index == -1)
-    self->tail_batch_index = batch->any.prev_batch_index;
-  else
-    self->batches.items[batch->any.next_batch_index].any.prev_batch_index = batch->any.prev_batch_index;
-
-  batch->any.prev_batch_index = -1;
-  batch->any.next_batch_index = -1;
-}
-
-static inline void
-gsk_ngl_command_queue_insert_before (GskNglCommandQueue *self,
-                                     GskNglCommandBatch *batch,
-                                     GskNglCommandBatch *sibling)
-{
-  int sibling_index;
-  int index;
-
-  g_assert (batch >= self->batches.items);
-  g_assert (batch < &self->batches.items[self->batches.len]);
-  g_assert (sibling >= self->batches.items);
-  g_assert (sibling < &self->batches.items[self->batches.len]);
-
-  index = gsk_ngl_command_batches_index_of (&self->batches, batch);
-  sibling_index = gsk_ngl_command_batches_index_of (&self->batches, sibling);
-
-  batch->any.next_batch_index = sibling_index;
-  batch->any.prev_batch_index = sibling->any.prev_batch_index;
-
-  if (batch->any.prev_batch_index > -1)
-    self->batches.items[batch->any.prev_batch_index].any.next_batch_index = index;
-
-  sibling->any.prev_batch_index = index;
-
-  if (batch->any.prev_batch_index == -1)
-    self->head_batch_index = index;
-}
-
-static void
-gsk_ngl_command_queue_sort_batches (GskNglCommandQueue *self)
-{
-  int *seen;
-  int *seen_free = NULL;
-  int index;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->tail_batch_index >= 0);
-  g_assert (self->fbo_max >= 0);
-
-  /* Create our seen list with most recent index set to -1,
-   * meaning we haven't yet seen that framebuffer.
-   */
-  if (self->fbo_max < 1024)
-    seen = g_alloca (sizeof (int) * (self->fbo_max + 1));
-  else
-    seen = seen_free = g_new0 (int, (self->fbo_max + 1));
-  for (int i = 0; i <= self->fbo_max; i++)
-    seen[i] = -1;
-
-  /* Walk in reverse, and if we've seen that framebuffer before, we want to
-   * delay this operation until right before the last batch we saw for that
-   * framebuffer.
-   *
-   * We can do this because we don't use a framebuffer's texture until it has
-   * been completely drawn.
-   */
-  index = self->tail_batch_index;
-
-  while (index >= 0)
-    {
-      GskNglCommandBatch *batch = &self->batches.items[index];
-      int cur_index = index;
-      int fbo = -1;
-
-      g_assert (index > -1);
-      g_assert (index < self->batches.len);
-
-      switch (batch->any.kind)
-        {
-        case GSK_NGL_COMMAND_KIND_DRAW:
-          fbo = batch->draw.framebuffer;
-          break;
-
-        case GSK_NGL_COMMAND_KIND_CLEAR:
-          fbo = batch->clear.framebuffer;
-          break;
-
-        default:
-          g_assert_not_reached ();
-        }
-
-      index = batch->any.prev_batch_index;
-
-      g_assert (index >= -1);
-      g_assert (index < (int)self->batches.len);
-      g_assert (fbo >= -1);
-
-      if (fbo == -1)
-        continue;
-
-      g_assert (fbo <= self->fbo_max);
-      g_assert (seen[fbo] >= -1);
-      g_assert (seen[fbo] < (int)self->batches.len);
-
-      if (seen[fbo] != -1 && seen[fbo] != batch->any.next_batch_index)
-        {
-          int mru_index = seen[fbo];
-          GskNglCommandBatch *mru = &self->batches.items[mru_index];
-
-          g_assert (mru_index > -1);
-
-          gsk_ngl_command_queue_unlink (self, batch);
-
-          g_assert (batch->any.prev_batch_index == -1);
-          g_assert (batch->any.next_batch_index == -1);
-
-          gsk_ngl_command_queue_insert_before (self, batch, mru);
-
-          g_assert (batch->any.prev_batch_index > -1 ||
-                    self->head_batch_index == cur_index);
-          g_assert (batch->any.next_batch_index == seen[fbo]);
-        }
-
-      g_assert (cur_index > -1);
-      g_assert (seen[fbo] >= -1);
-
-      seen[fbo] = cur_index;
-    }
-
-  g_free (seen_free);
-}
-
-/**
- * gsk_ngl_command_queue_execute:
- * @self: a `GskNglCommandQueue`
- * @surface_height: the height of the backing surface
- * @scale_factor: the scale factor of the backing surface
- * #scissor: (nullable): the scissor clip if any
- *
- * Executes all of the batches in the command queue.
- */
-void
-gsk_ngl_command_queue_execute (GskNglCommandQueue   *self,
-                               guint                 surface_height,
-                               guint                 scale_factor,
-                               const cairo_region_t *scissor)
-{
-  G_GNUC_UNUSED guint count = 0;
-  graphene_rect_t scissor_test;
-  gboolean has_scissor = scissor != NULL;
-  gboolean scissor_state = -1;
-  guint program = 0;
-  guint width = 0;
-  guint height = 0;
-  guint n_binds = 0;
-  guint n_fbos = 0;
-  guint n_uniforms = 0;
-  guint n_programs = 0;
-  guint vao_id;
-  guint vbo_id;
-  int textures[4];
-  int framebuffer = -1;
-  int next_batch_index;
-  int active = -1;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->in_draw == FALSE);
-
-  if (self->batches.len == 0)
-    return;
-
-  for (guint i = 0; i < G_N_ELEMENTS (textures); i++)
-    textures[i] = -1;
-
-  gsk_ngl_command_queue_sort_batches (self);
-
-  gsk_ngl_command_queue_make_current (self);
-
-#ifdef G_ENABLE_DEBUG
-  gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
-  gsk_profiler_timer_begin (self->profiler, self->metrics.cpu_time);
-#endif
-
-  glEnable (GL_DEPTH_TEST);
-  glDepthFunc (GL_LEQUAL);
-
-  /* Pre-multiplied alpha */
-  glEnable (GL_BLEND);
-  glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-  glBlendEquation (GL_FUNC_ADD);
-
-  glGenVertexArrays (1, &vao_id);
-  glBindVertexArray (vao_id);
-
-  vbo_id = gsk_ngl_buffer_submit (&self->vertices);
-
-  /* 0 = position location */
-  glEnableVertexAttribArray (0);
-  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskNglDrawVertex),
-                         (void *) G_STRUCT_OFFSET (GskNglDrawVertex, position));
-
-  /* 1 = texture coord location */
-  glEnableVertexAttribArray (1);
-  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
-                         sizeof (GskNglDrawVertex),
-                         (void *) G_STRUCT_OFFSET (GskNglDrawVertex, uv));
-
-  /* 2 = color location */
-  glEnableVertexAttribArray (2);
-  glVertexAttribPointer (2, 4, GL_HALF_FLOAT, GL_FALSE,
-                         sizeof (GskNglDrawVertex),
-                         (void *) G_STRUCT_OFFSET (GskNglDrawVertex, color));
-
-  /* 3 = color2 location */
-  glEnableVertexAttribArray (3);
-  glVertexAttribPointer (3, 4, GL_HALF_FLOAT, GL_FALSE,
-                         sizeof (GskNglDrawVertex),
-                         (void *) G_STRUCT_OFFSET (GskNglDrawVertex, color2));
-
-  /* Setup initial scissor clip */
-  if (scissor != NULL)
-    {
-      cairo_rectangle_int_t r;
-
-      g_assert (cairo_region_num_rectangles (scissor) == 1);
-      cairo_region_get_rectangle (scissor, 0, &r);
-
-      scissor_test.origin.x = r.x * scale_factor;
-      scissor_test.origin.y = surface_height - (r.height * scale_factor) - (r.y * scale_factor);
-      scissor_test.size.width = r.width * scale_factor;
-      scissor_test.size.height = r.height * scale_factor;
-    }
-
-  next_batch_index = self->head_batch_index;
-
-  while (next_batch_index >= 0)
-    {
-      const GskNglCommandBatch *batch = &self->batches.items[next_batch_index];
-
-      g_assert (next_batch_index >= 0);
-      g_assert (next_batch_index < self->batches.len);
-      g_assert (batch->any.next_batch_index != next_batch_index);
-
-      count++;
-
-      switch (batch->any.kind)
-        {
-        case GSK_NGL_COMMAND_KIND_CLEAR:
-          if (apply_framebuffer (&framebuffer, batch->clear.framebuffer))
-            {
-              apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
-              n_fbos++;
-            }
-
-          apply_viewport (&width,
-                          &height,
-                          batch->any.viewport.width,
-                          batch->any.viewport.height);
-
-          glClearColor (0, 0, 0, 0);
-          glClear (batch->clear.bits);
-        break;
-
-        case GSK_NGL_COMMAND_KIND_DRAW:
-          if (batch->any.program != program)
-            {
-              program = batch->any.program;
-              glUseProgram (program);
-
-              n_programs++;
-            }
-
-          if (apply_framebuffer (&framebuffer, batch->draw.framebuffer))
-            {
-              apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
-              n_fbos++;
-            }
-
-          apply_viewport (&width,
-                          &height,
-                          batch->any.viewport.width,
-                          batch->any.viewport.height);
-
-          if G_UNLIKELY (batch->draw.bind_count > 0)
-            {
-              const GskNglCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset];
-
-              for (guint i = 0; i < batch->draw.bind_count; i++)
-                {
-                  if (textures[bind->texture] != bind->id)
-                    {
-                      if (active != bind->texture)
-                        {
-                          active = bind->texture;
-                          glActiveTexture (GL_TEXTURE0 + bind->texture);
-                        }
-
-                      glBindTexture (GL_TEXTURE_2D, bind->id);
-                      textures[bind->texture] = bind->id;
-                    }
-
-                  bind++;
-                }
-
-              n_binds += batch->draw.bind_count;
-            }
-
-          if (batch->draw.uniform_count > 0)
-            {
-              const GskNglCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset];
-
-              for (guint i = 0; i < batch->draw.uniform_count; i++, u++)
-                gsk_ngl_uniform_state_apply (self->uniforms, program, u->location, u->info);
-
-              n_uniforms += batch->draw.uniform_count;
-            }
-
-          glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count);
-
-        break;
-
-        default:
-          g_assert_not_reached ();
-        }
-
-#if 0
-      if (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW ||
-          batch->any.kind == GSK_NGL_COMMAND_KIND_CLEAR)
-        {
-          char filename[128];
-          g_snprintf (filename, sizeof filename,
-                      "capture%03u_batch%03d_kind%u_program%u_u%u_b%u_fb%u_ctx%p.png",
-                      count, next_batch_index,
-                      batch->any.kind, batch->any.program,
-                      batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0,
-                      batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0,
-                      framebuffer,
-                      gdk_gl_context_get_current ());
-          gsk_ngl_command_queue_capture_png (self, filename, width, height, TRUE);
-          gsk_ngl_command_queue_print_batch (self, batch);
-        }
-#endif
-
-      next_batch_index = batch->any.next_batch_index;
-    }
-
-  glDeleteBuffers (1, &vbo_id);
-  glDeleteVertexArrays (1, &vao_id);
-
-  gdk_profiler_set_int_counter (self->metrics.n_binds, n_binds);
-  gdk_profiler_set_int_counter (self->metrics.n_uniforms, n_uniforms);
-  gdk_profiler_set_int_counter (self->metrics.n_fbos, n_fbos);
-  gdk_profiler_set_int_counter (self->metrics.n_programs, n_programs);
-  gdk_profiler_set_int_counter (self->metrics.n_uploads, self->n_uploads);
-  gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len);
-
-#ifdef G_ENABLE_DEBUG
-  {
-    gint64 start_time G_GNUC_UNUSED = gsk_profiler_timer_get_start (self->profiler, self->metrics.cpu_time);
-    gint64 cpu_time = gsk_profiler_timer_end (self->profiler, self->metrics.cpu_time);
-    gint64 gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler);
-
-    gsk_profiler_timer_set (self->profiler, self->metrics.gpu_time, gpu_time);
-    gsk_profiler_timer_set (self->profiler, self->metrics.cpu_time, cpu_time);
-    gsk_profiler_counter_inc (self->profiler, self->metrics.n_frames);
-
-    gsk_profiler_push_samples (self->profiler);
-  }
-#endif
-}
-
-void
-gsk_ngl_command_queue_begin_frame (GskNglCommandQueue *self)
-{
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (self->batches.len == 0);
-
-  gsk_ngl_command_queue_make_current (self);
-
-  self->fbo_max = 0;
-  self->tail_batch_index = -1;
-  self->head_batch_index = -1;
-  self->in_frame = TRUE;
-}
-
-/**
- * gsk_ngl_command_queue_end_frame:
- * @self: a `GskNglCommandQueue`
- *
- * This function performs cleanup steps that need to be done after
- * a frame has finished. This is not performed as part of the command
- * queue execution to allow for the frame to be submitted as soon
- * as possible.
- *
- * However, it should be executed after the draw contexts end_frame
- * has been called to swap the OpenGL framebuffers.
- */
-void
-gsk_ngl_command_queue_end_frame (GskNglCommandQueue *self)
-{
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  gsk_ngl_command_queue_make_current (self);
-  gsk_ngl_uniform_state_end_frame (self->uniforms);
-
-  /* Reset attachments so we don't hold on to any textures
-   * that might be released after the frame.
-   */
-  for (guint i = 0; i < G_N_ELEMENTS (self->attachments->textures); i++)
-    {
-      if (self->attachments->textures[i].id != 0)
-        {
-          glActiveTexture (GL_TEXTURE0 + i);
-          glBindTexture (GL_TEXTURE_2D, 0);
-
-          self->attachments->textures[i].id = 0;
-          self->attachments->textures[i].changed = FALSE;
-          self->attachments->textures[i].initial = TRUE;
-        }
-    }
-
-  self->batches.len = 0;
-  self->batch_binds.len = 0;
-  self->batch_uniforms.len = 0;
-  self->n_uploads = 0;
-  self->tail_batch_index = -1;
-  self->in_frame = FALSE;
-}
-
-gboolean
-gsk_ngl_command_queue_create_render_target (GskNglCommandQueue *self,
-                                            int                 width,
-                                            int                 height,
-                                            int                 format,
-                                            int                 min_filter,
-                                            int                 mag_filter,
-                                            guint              *out_fbo_id,
-                                            guint              *out_texture_id)
-{
-  GLuint fbo_id = 0;
-  GLint texture_id;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (width > 0);
-  g_assert (height > 0);
-  g_assert (out_fbo_id != NULL);
-  g_assert (out_texture_id != NULL);
-
-  texture_id = gsk_ngl_command_queue_create_texture (self,
-                                                     width, height,
-                                                     format,
-                                                     min_filter, mag_filter);
-
-  if (texture_id == -1)
-    {
-      *out_fbo_id = 0;
-      *out_texture_id = 0;
-      return FALSE;
-    }
-
-  fbo_id = gsk_ngl_command_queue_create_framebuffer (self);
-
-  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
-  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
-  g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
-
-  *out_fbo_id = fbo_id;
-  *out_texture_id = texture_id;
-
-  return TRUE;
-}
-
-int
-gsk_ngl_command_queue_create_texture (GskNglCommandQueue *self,
-                                      int                 width,
-                                      int                 height,
-                                      int                 format,
-                                      int                 min_filter,
-                                      int                 mag_filter)
-{
-  GLuint texture_id = 0;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  if G_UNLIKELY (self->max_texture_size == -1)
-    glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size);
-
-  if (width > self->max_texture_size || height > self->max_texture_size)
-    return -1;
-
-  glGenTextures (1, &texture_id);
-
-  glActiveTexture (GL_TEXTURE0);
-  glBindTexture (GL_TEXTURE_2D, texture_id);
-
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-  if (gdk_gl_context_get_use_es (self->context))
-    glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-  else
-    glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
-
-  /* Restore the previous texture if it was set */
-  if (self->attachments->textures[0].id != 0)
-    glBindTexture (GL_TEXTURE_2D, self->attachments->textures[0].id);
-
-  return (int)texture_id;
-}
-
-guint
-gsk_ngl_command_queue_create_framebuffer (GskNglCommandQueue *self)
-{
-  GLuint fbo_id;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-
-  glGenFramebuffers (1, &fbo_id);
-
-  return fbo_id;
-}
-
-int
-gsk_ngl_command_queue_upload_texture (GskNglCommandQueue *self,
-                                      GdkTexture         *texture,
-                                      guint               x_offset,
-                                      guint               y_offset,
-                                      guint               width,
-                                      guint               height,
-                                      int                 min_filter,
-                                      int                 mag_filter)
-{
-  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
-  cairo_surface_t *surface = NULL;
-  GdkMemoryFormat data_format;
-  const guchar *data;
-  gsize data_stride;
-  gsize bpp;
-  int texture_id;
-
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (!GDK_IS_GL_TEXTURE (texture));
-  g_assert (x_offset + width <= gdk_texture_get_width (texture));
-  g_assert (y_offset + height <= gdk_texture_get_height (texture));
-  g_assert (min_filter == GL_LINEAR || min_filter == GL_NEAREST);
-  g_assert (mag_filter == GL_LINEAR || min_filter == GL_NEAREST);
-
-  if (width > self->max_texture_size || height > self->max_texture_size)
-    {
-      g_warning ("Attempt to create texture of size %ux%u but max size is %d. "
-                 "Clipping will occur.",
-                 width, height, self->max_texture_size);
-      width = MAX (width, self->max_texture_size);
-      height = MAX (height, self->max_texture_size);
-    }
-
-  texture_id = gsk_ngl_command_queue_create_texture (self, width, height, GL_RGBA8, min_filter, mag_filter);
-  if (texture_id == -1)
-    return texture_id;
-
-  if (GDK_IS_MEMORY_TEXTURE (texture))
-    {
-      GdkMemoryTexture *memory_texture = GDK_MEMORY_TEXTURE (texture);
-      data = gdk_memory_texture_get_data (memory_texture);
-      data_format = gdk_texture_get_format (texture);
-      data_stride = gdk_memory_texture_get_stride (memory_texture);
-    }
-  else
-    {
-      /* Fall back to downloading to a surface */
-      surface = gdk_texture_download_surface (texture);
-      cairo_surface_flush (surface);
-      data = cairo_image_surface_get_data (surface);
-      data_format = GDK_MEMORY_DEFAULT;
-      data_stride = cairo_image_surface_get_stride (surface);
-    }
-
-  self->n_uploads++;
-
-  bpp = gdk_memory_format_bytes_per_pixel (data_format);
-
-  /* Switch to texture0 as 2D. We'll restore it later. */
-  glActiveTexture (GL_TEXTURE0);
-  glBindTexture (GL_TEXTURE_2D, texture_id);
-
-  gdk_gl_context_upload_texture (gdk_gl_context_get_current (),
-                                 data + x_offset * bpp + y_offset * data_stride,
-                                 width, height, data_stride,
-                                 data_format, GL_TEXTURE_2D);
-
-  /* Restore previous texture state if any */
-  if (self->attachments->textures[0].id > 0)
-    glBindTexture (self->attachments->textures[0].target,
-                   self->attachments->textures[0].id);
-
-  g_clear_pointer (&surface, cairo_surface_destroy);
-
-  if (gdk_profiler_is_running ())
-    gdk_profiler_add_markf (start_time, GDK_PROFILER_CURRENT_TIME-start_time,
-                            "Upload Texture",
-                            "Size %dx%d", width, height);
-
-  return texture_id;
-}
-
-void
-gsk_ngl_command_queue_set_profiler (GskNglCommandQueue *self,
-                                    GskProfiler        *profiler)
-{
-#ifdef G_ENABLE_DEBUG
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
-  g_assert (GSK_IS_PROFILER (profiler));
-
-  if (g_set_object (&self->profiler, profiler))
-    {
-      self->gl_profiler = gsk_gl_profiler_new (self->context);
-
-      self->metrics.n_frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE);
-      self->metrics.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU Time", FALSE, TRUE);
-      self->metrics.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU Time", FALSE, TRUE);
-
-      self->metrics.n_binds = gdk_profiler_define_int_counter ("attachments", "Number of texture attachments");
-      self->metrics.n_fbos = gdk_profiler_define_int_counter ("fbos", "Number of framebuffers attached");
-      self->metrics.n_uniforms = gdk_profiler_define_int_counter ("uniforms", "Number of uniforms changed");
-      self->metrics.n_uploads = gdk_profiler_define_int_counter ("uploads", "Number of texture uploads");
-      self->metrics.n_programs = gdk_profiler_define_int_counter ("programs", "Number of program changes");
-      self->metrics.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth", "Depth of GL command batches");
-    }
-#endif
-}
diff --git a/gsk/ngl/gsknglcommandqueueprivate.h b/gsk/ngl/gsknglcommandqueueprivate.h
deleted file mode 100644 (file)
index 40cca01..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/* gsknglcommandqueueprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
-#define __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
-
-#include <gsk/gskprofilerprivate.h>
-
-#include "gskngltypesprivate.h"
-#include "gsknglbufferprivate.h"
-#include "gsknglattachmentstateprivate.h"
-#include "gskngluniformstateprivate.h"
-
-#include "inlinearray.h"
-
-#include "gskglprofilerprivate.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_ngl_command_queue_get_type())
-
-G_DECLARE_FINAL_TYPE (GskNglCommandQueue, gsk_ngl_command_queue, GSK, NGL_COMMAND_QUEUE, GObject)
-
-typedef enum _GskNglCommandKind
-{
-  /* The batch will perform a glClear() */
-  GSK_NGL_COMMAND_KIND_CLEAR,
-
-  /* The batch will perform a glDrawArrays() */
-  GSK_NGL_COMMAND_KIND_DRAW,
-} GskNglCommandKind;
-
-typedef struct _GskNglCommandBind
-{
-  /* @texture is the value passed to glActiveTexture(), the "slot" the
-   * texture will be placed into. We always use GL_TEXTURE_2D so we don't
-   * waste any bits here to indicate that.
-   */
-  guint texture : 5;
-
-  /* The identifier for the texture created with glGenTextures(). */
-  guint id : 27;
-} GskNglCommandBind;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandBind) == 4);
-
-typedef struct _GskNglCommandBatchAny
-{
-  /* A GskNglCommandKind indicating what the batch will do */
-  guint kind : 8;
-
-  /* The program's identifier to use for determining if we can merge two
-   * batches together into a single set of draw operations. We put this
-   * here instead of the GskNglCommandDraw so that we can use the extra
-   * bits here without making the structure larger.
-   */
-  guint program : 24;
-
-  /* The index of the next batch following this one. This is used
-   * as a sort of integer-based linked list to simplify out-of-order
-   * batching without moving memory around. -1 indicates last batch.
-   */
-  gint16 next_batch_index;
-
-  /* Same but for reverse direction as we sort in reverse to get the
-   * batches ordered by framebuffer.
-   */
-  gint16 prev_batch_index;
-
-  /* The viewport size of the batch. We check this as we process
-   * batches to determine if we need to resize the viewport.
-   */
-  struct {
-    guint16 width;
-    guint16 height;
-  } viewport;
-} GskNglCommandBatchAny;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandBatchAny) == 12);
-
-typedef struct _GskNglCommandDraw
-{
-  GskNglCommandBatchAny head;
-
-  /* There doesn't seem to be a limit on the framebuffer identifier that
-   * can be returned, so we have to use a whole unsigned for the framebuffer
-   * we are drawing to. When processing batches, we check to see if this
-   * changes and adjust the render target accordingly. Some sorting is
-   * performed to reduce the amount we change framebuffers.
-   */
-  guint framebuffer;
-
-  /* The number of uniforms to change. This must be less than or equal to
-   * GL_MAX_UNIFORM_LOCATIONS but only guaranteed up to 1024 by any OpenGL
-   * implementation to be conformant.
-   */
-  guint uniform_count : 11;
-
-  /* The number of textures to bind, which is only guaranteed up to 16
-   * by the OpenGL specification to be conformant.
-   */
-  guint bind_count : 5;
-
-  /* GL_MAX_ELEMENTS_VERTICES specifies 33000 for this which requires 16-bit
-   * to address all possible counts <= GL_MAX_ELEMENTS_VERTICES.
-   */
-  guint vbo_count : 16;
-
-  /* The offset within the VBO containing @vbo_count vertices to send with
-   * glDrawArrays().
-   */
-  guint vbo_offset;
-
-  /* The offset within the array of uniform changes to be made containing
-   * @uniform_count `GskNglCommandUniform` elements to apply.
-   */
-  guint uniform_offset;
-
-  /* The offset within the array of bind changes to be made containing
-   * @bind_count `GskNglCommandBind` elements to apply.
-   */
-  guint bind_offset;
-} GskNglCommandDraw;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandDraw) == 32);
-
-typedef struct _GskNglCommandClear
-{
-  GskNglCommandBatchAny  any;
-  guint                 bits;
-  guint                 framebuffer;
-} GskNglCommandClear;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandClear) == 20);
-
-typedef struct _GskNglCommandUniform
-{
-  GskNglUniformInfo info;
-  guint             location;
-} GskNglCommandUniform;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandUniform) == 8);
-
-typedef union _GskNglCommandBatch
-{
-  GskNglCommandBatchAny any;
-  GskNglCommandDraw     draw;
-  GskNglCommandClear    clear;
-} GskNglCommandBatch;
-
-G_STATIC_ASSERT (sizeof (GskNglCommandBatch) == 32);
-
-DEFINE_INLINE_ARRAY (GskNglCommandBatches, gsk_ngl_command_batches, GskNglCommandBatch)
-DEFINE_INLINE_ARRAY (GskNglCommandBinds, gsk_ngl_command_binds, GskNglCommandBind)
-DEFINE_INLINE_ARRAY (GskNglCommandUniforms, gsk_ngl_command_uniforms, GskNglCommandUniform)
-
-struct _GskNglCommandQueue
-{
-  GObject parent_instance;
-
-  /* The GdkGLContext we make current before executing GL commands. */
-  GdkGLContext *context;
-
-  /* Array of GskNglCommandBatch which is a fixed size structure that will
-   * point into offsets of other arrays so that all similar data is stored
-   * together. The idea here is that we reduce the need for pointers so that
-   * using g_realloc()'d arrays is fine.
-   */
-  GskNglCommandBatches batches;
-
-  /* Contains array of vertices and some wrapper code to help upload them
-   * to the GL driver. We can also tweak this to use double buffered arrays
-   * if we find that to be faster on some hardware and/or drivers.
-   */
-  GskNglBuffer vertices;
-
-  /* The GskNglAttachmentState contains information about our FBO and texture
-   * attachments as we process incoming operations. We snapshot them into
-   * various batches so that we can compare differences between merge
-   * candidates.
-   */
-  GskNglAttachmentState *attachments;
-
-  /* The uniform state across all programs. We snapshot this into batches so
-   * that we can compare uniform state between batches to give us more
-   * chances at merging draw commands.
-   */
-  GskNglUniformState *uniforms;
-
-  /* Current program if we are in a draw so that we can send commands
-   * to the uniform state as needed.
-   */
-  GskNglUniformProgram *program_info;
-
-  /* The profiler instance to deliver timing/etc data */
-  GskProfiler *profiler;
-  GskGLProfiler *gl_profiler;
-
-  /* Array of GskNglCommandBind which denote what textures need to be attached
-   * to which slot. GskNglCommandDraw.bind_offset and bind_count reference this
-   * array to determine what to attach.
-   */
-  GskNglCommandBinds batch_binds;
-
-  /* Array of GskNglCommandUniform denoting which uniforms must be updated
-   * before the glDrawArrays() may be called. These are referenced from the
-   * GskNglCommandDraw.uniform_offset and uniform_count fields.
-   */
-  GskNglCommandUniforms batch_uniforms;
-
-  /* Discovered max texture size when loading the command queue so that we
-   * can either scale down or slice textures to fit within this size. Assumed
-   * to be both height and width.
-   */
-  int max_texture_size;
-
-  /* The index of the last batch in @batches, which may not be the element
-   * at the end of the array, as batches can be reordered. This is used to
-   * update the "next" index when adding a new batch.
-   */
-  gint16 tail_batch_index;
-  gint16 head_batch_index;
-
-  /* Max framebuffer we used, so we can sort items faster */
-  guint fbo_max;
-
-  /* Various GSK and GDK metric counter ids */
-  struct {
-    GQuark n_frames;
-    GQuark cpu_time;
-    GQuark gpu_time;
-    guint n_binds;
-    guint n_fbos;
-    guint n_uniforms;
-    guint n_uploads;
-    guint n_programs;
-    guint queue_depth;
-  } metrics;
-
-  /* Counter for uploads on the frame */
-  guint n_uploads;
-
-  /* If we're inside a begin/end_frame pair */
-  guint in_frame : 1;
-
-  /* If we're inside of a begin_draw()/end_draw() pair. */
-  guint in_draw : 1;
-
-  /* If we've warned about truncating batches */
-  guint have_truncated : 1;
-};
-
-GskNglCommandQueue *gsk_ngl_command_queue_new                  (GdkGLContext          *context,
-                                                                GskNglUniformState    *uniforms);
-void                gsk_ngl_command_queue_set_profiler         (GskNglCommandQueue    *self,
-                                                                GskProfiler           *profiler);
-GdkGLContext       *gsk_ngl_command_queue_get_context          (GskNglCommandQueue    *self);
-void                gsk_ngl_command_queue_make_current         (GskNglCommandQueue    *self);
-void                gsk_ngl_command_queue_begin_frame          (GskNglCommandQueue    *self);
-void                gsk_ngl_command_queue_end_frame            (GskNglCommandQueue    *self);
-void                gsk_ngl_command_queue_execute              (GskNglCommandQueue    *self,
-                                                                guint                  surface_height,
-                                                                guint                  scale_factor,
-                                                                const cairo_region_t  *scissor);
-int                 gsk_ngl_command_queue_upload_texture       (GskNglCommandQueue    *self,
-                                                                GdkTexture            *texture,
-                                                                guint                  x_offset,
-                                                                guint                  y_offset,
-                                                                guint                  width,
-                                                                guint                  height,
-                                                                int                    min_filter,
-                                                                int                    mag_filter);
-int                 gsk_ngl_command_queue_create_texture       (GskNglCommandQueue    *self,
-                                                                int                    width,
-                                                                int                    height,
-                                                                int                    format,
-                                                                int                    min_filter,
-                                                                int                    mag_filter);
-guint               gsk_ngl_command_queue_create_framebuffer   (GskNglCommandQueue    *self);
-gboolean            gsk_ngl_command_queue_create_render_target (GskNglCommandQueue    *self,
-                                                                int                    width,
-                                                                int                    height,
-                                                                int                    format,
-                                                                int                    min_filter,
-                                                                int                    mag_filter,
-                                                                guint                 *out_fbo_id,
-                                                                guint                 *out_texture_id);
-void                gsk_ngl_command_queue_delete_program       (GskNglCommandQueue    *self,
-                                                                guint                  program_id);
-void                gsk_ngl_command_queue_clear                (GskNglCommandQueue    *self,
-                                                                guint                  clear_bits,
-                                                                const graphene_rect_t *viewport);
-void                gsk_ngl_command_queue_begin_draw           (GskNglCommandQueue    *self,
-                                                                GskNglUniformProgram  *program_info,
-                                                                guint                  width,
-                                                                guint                  height);
-void                gsk_ngl_command_queue_end_draw             (GskNglCommandQueue    *self);
-void                gsk_ngl_command_queue_split_draw           (GskNglCommandQueue    *self);
-
-static inline GskNglCommandBatch *
-gsk_ngl_command_queue_get_batch (GskNglCommandQueue *self)
-{
-  return gsk_ngl_command_batches_tail (&self->batches);
-}
-
-static inline GskNglDrawVertex *
-gsk_ngl_command_queue_add_vertices (GskNglCommandQueue *self)
-{
-  gsk_ngl_command_queue_get_batch (self)->draw.vbo_count += GSK_NGL_N_VERTICES;
-  return gsk_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES);
-}
-
-static inline GskNglDrawVertex *
-gsk_ngl_command_queue_add_n_vertices (GskNglCommandQueue *self,
-                                      guint               count)
-{
-  /* This is a batch form of gsk_ngl_command_queue_add_vertices(). Note that
-   * it does *not* add the count to .draw.vbo_count as the caller is responsible
-   * for that.
-   */
-  return gsk_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES * count);
-}
-
-static inline void
-gsk_ngl_command_queue_retract_n_vertices (GskNglCommandQueue *self,
-                                          guint               count)
-{
-  /* Like gsk_ngl_command_queue_add_n_vertices(), this does not tweak
-   * the draw vbo_count.
-   */
-  gsk_ngl_buffer_retract (&self->vertices, GSK_NGL_N_VERTICES * count);
-}
-
-static inline guint
-gsk_ngl_command_queue_bind_framebuffer (GskNglCommandQueue *self,
-                                        guint               framebuffer)
-{
-  guint ret = self->attachments->fbo.id;
-  gsk_ngl_attachment_state_bind_framebuffer (self->attachments, framebuffer);
-  return ret;
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglcompiler.c b/gsk/ngl/gsknglcompiler.c
deleted file mode 100644 (file)
index 8b1791f..0000000
+++ /dev/null
@@ -1,683 +0,0 @@
-/* gsknglcompiler.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gsk/gskdebugprivate.h>
-#include <gio/gio.h>
-#include <string.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gsknglcompilerprivate.h"
-#include "gsknglprogramprivate.h"
-
-#define SHADER_VERSION_GLES       100
-#define SHADER_VERSION_GL2_LEGACY 110
-#define SHADER_VERSION_GL3_LEGACY 130
-#define SHADER_VERSION_GL3        150
-
-struct _GskNglCompiler
-{
-  GObject parent_instance;
-
-  GskNglDriver *driver;
-
-  GBytes *all_preamble;
-  GBytes *fragment_preamble;
-  GBytes *vertex_preamble;
-  GBytes *fragment_source;
-  GBytes *fragment_suffix;
-  GBytes *vertex_source;
-  GBytes *vertex_suffix;
-
-  GArray *attrib_locations;
-
-  int glsl_version;
-
-  guint gl3 : 1;
-  guint gles : 1;
-  guint legacy : 1;
-  guint debug_shaders : 1;
-};
-
-typedef struct _GskNglProgramAttrib
-{
-  const char *name;
-  guint location;
-} GskNglProgramAttrib;
-
-static GBytes *empty_bytes;
-
-G_DEFINE_TYPE (GskNglCompiler, gsk_ngl_compiler, G_TYPE_OBJECT)
-
-static void
-gsk_ngl_compiler_finalize (GObject *object)
-{
-  GskNglCompiler *self = (GskNglCompiler *)object;
-
-  g_clear_pointer (&self->all_preamble, g_bytes_unref);
-  g_clear_pointer (&self->fragment_preamble, g_bytes_unref);
-  g_clear_pointer (&self->vertex_preamble, g_bytes_unref);
-  g_clear_pointer (&self->vertex_suffix, g_bytes_unref);
-  g_clear_pointer (&self->fragment_source, g_bytes_unref);
-  g_clear_pointer (&self->fragment_suffix, g_bytes_unref);
-  g_clear_pointer (&self->vertex_source, g_bytes_unref);
-  g_clear_pointer (&self->attrib_locations, g_array_unref);
-  g_clear_object (&self->driver);
-
-  G_OBJECT_CLASS (gsk_ngl_compiler_parent_class)->finalize (object);
-}
-
-static void
-gsk_ngl_compiler_class_init (GskNglCompilerClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->finalize = gsk_ngl_compiler_finalize;
-
-  empty_bytes = g_bytes_new (NULL, 0);
-}
-
-static void
-gsk_ngl_compiler_init (GskNglCompiler *self)
-{
-  self->glsl_version = 150;
-  self->attrib_locations = g_array_new (FALSE, FALSE, sizeof (GskNglProgramAttrib));
-  self->all_preamble = g_bytes_ref (empty_bytes);
-  self->vertex_preamble = g_bytes_ref (empty_bytes);
-  self->fragment_preamble = g_bytes_ref (empty_bytes);
-  self->vertex_source = g_bytes_ref (empty_bytes);
-  self->vertex_suffix = g_bytes_ref (empty_bytes);
-  self->fragment_source = g_bytes_ref (empty_bytes);
-  self->fragment_suffix = g_bytes_ref (empty_bytes);
-}
-
-GskNglCompiler *
-gsk_ngl_compiler_new (GskNglDriver *driver,
-                      gboolean      debug_shaders)
-{
-  GskNglCompiler *self;
-  GdkGLContext *context;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-  g_return_val_if_fail (driver->shared_command_queue != NULL, NULL);
-
-  self = g_object_new (GSK_TYPE_GL_COMPILER, NULL);
-  self->driver = g_object_ref (driver);
-  self->debug_shaders = !!debug_shaders;
-
-  context = gsk_ngl_command_queue_get_context (self->driver->shared_command_queue);
-
-  if (gdk_gl_context_get_use_es (context))
-    {
-      self->glsl_version = SHADER_VERSION_GLES;
-      self->gles = TRUE;
-    }
-  else if (gdk_gl_context_is_legacy (context))
-    {
-      int maj, min;
-
-      gdk_gl_context_get_version (context, &maj, &min);
-
-      if (maj == 3)
-        self->glsl_version = SHADER_VERSION_GL3_LEGACY;
-      else
-        self->glsl_version = SHADER_VERSION_GL2_LEGACY;
-
-      self->legacy = TRUE;
-    }
-  else
-    {
-      self->glsl_version = SHADER_VERSION_GL3;
-      self->gl3 = TRUE;
-    }
-
-  gsk_ngl_command_queue_make_current (self->driver->shared_command_queue);
-
-  return g_steal_pointer (&self);
-}
-
-void
-gsk_ngl_compiler_bind_attribute (GskNglCompiler *self,
-                                 const char     *name,
-                                 guint           location)
-{
-  GskNglProgramAttrib attrib;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (name != NULL);
-  g_return_if_fail (location < 32);
-
-  attrib.name = g_intern_string (name);
-  attrib.location = location;
-
-  g_array_append_val (self->attrib_locations, attrib);
-}
-
-void
-gsk_ngl_compiler_clear_attributes (GskNglCompiler *self)
-{
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-
-  g_array_set_size (self->attrib_locations, 0);
-}
-
-void
-gsk_ngl_compiler_set_preamble (GskNglCompiler     *self,
-                               GskNglCompilerKind  kind,
-                               GBytes             *preamble_bytes)
-{
-  GBytes **loc = NULL;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (preamble_bytes != NULL);
-
-  if (kind == GSK_NGL_COMPILER_ALL)
-    loc = &self->all_preamble;
-  else if (kind == GSK_NGL_COMPILER_FRAGMENT)
-    loc = &self->fragment_preamble;
-  else if (kind == GSK_NGL_COMPILER_VERTEX)
-    loc = &self->vertex_preamble;
-  else
-    g_return_if_reached ();
-
-  g_assert (loc != NULL);
-
-  if (*loc != preamble_bytes)
-    {
-      g_clear_pointer (loc, g_bytes_unref);
-      *loc = preamble_bytes ? g_bytes_ref (preamble_bytes) : NULL;
-    }
-}
-
-void
-gsk_ngl_compiler_set_preamble_from_resource (GskNglCompiler     *self,
-                                             GskNglCompilerKind  kind,
-                                             const char         *resource_path)
-{
-  GError *error = NULL;
-  GBytes *bytes;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
-                    kind == GSK_NGL_COMPILER_VERTEX ||
-                    kind == GSK_NGL_COMPILER_FRAGMENT);
-  g_return_if_fail (resource_path != NULL);
-
-  bytes = g_resources_lookup_data (resource_path,
-                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
-                                   &error);
-
-  if (bytes == NULL)
-    g_warning ("Cannot set shader from resource: %s", error->message);
-  else
-    gsk_ngl_compiler_set_preamble (self, kind, bytes);
-
-  g_clear_pointer (&bytes, g_bytes_unref);
-  g_clear_error (&error);
-}
-
-void
-gsk_ngl_compiler_set_source (GskNglCompiler     *self,
-                             GskNglCompilerKind  kind,
-                             GBytes             *source_bytes)
-{
-  GBytes **loc = NULL;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
-                    kind == GSK_NGL_COMPILER_VERTEX ||
-                    kind == GSK_NGL_COMPILER_FRAGMENT);
-
-  if (source_bytes == NULL)
-    source_bytes = empty_bytes;
-
-  /* If kind is ALL, then we need to split the fragment and
-   * vertex shaders from the bytes and assign them individually.
-   * This safely scans for FRAGMENT_SHADER and VERTEX_SHADER as
-   * specified within the GLSL resources. Some care is taken to
-   * use GBytes which reference the original bytes instead of
-   * copying them.
-   */
-  if (kind == GSK_NGL_COMPILER_ALL)
-    {
-      gsize len = 0;
-      const char *source;
-      const char *vertex_shader_start;
-      const char *fragment_shader_start;
-      const char *endpos;
-      GBytes *fragment_bytes;
-      GBytes *vertex_bytes;
-
-      g_clear_pointer (&self->fragment_source, g_bytes_unref);
-      g_clear_pointer (&self->vertex_source, g_bytes_unref);
-
-      source = g_bytes_get_data (source_bytes, &len);
-      endpos = source + len;
-      vertex_shader_start = g_strstr_len (source, len, "VERTEX_SHADER");
-      fragment_shader_start = g_strstr_len (source, len, "FRAGMENT_SHADER");
-
-      if (vertex_shader_start == NULL)
-        {
-          g_warning ("Failed to locate VERTEX_SHADER in shader source");
-          return;
-        }
-
-      if (fragment_shader_start == NULL)
-        {
-          g_warning ("Failed to locate FRAGMENT_SHADER in shader source");
-          return;
-        }
-
-      if (vertex_shader_start > fragment_shader_start)
-        {
-          g_warning ("VERTEX_SHADER must come before FRAGMENT_SHADER");
-          return;
-        }
-
-      /* Locate next newlines */
-      while (vertex_shader_start < endpos && vertex_shader_start[0] != '\n')
-        vertex_shader_start++;
-      while (fragment_shader_start < endpos && fragment_shader_start[0] != '\n')
-        fragment_shader_start++;
-
-      vertex_bytes = g_bytes_new_from_bytes (source_bytes,
-                                             vertex_shader_start - source,
-                                             fragment_shader_start - vertex_shader_start);
-      fragment_bytes = g_bytes_new_from_bytes (source_bytes,
-                                               fragment_shader_start - source,
-                                               endpos - fragment_shader_start);
-
-      gsk_ngl_compiler_set_source (self, GSK_NGL_COMPILER_VERTEX, vertex_bytes);
-      gsk_ngl_compiler_set_source (self, GSK_NGL_COMPILER_FRAGMENT, fragment_bytes);
-
-      g_bytes_unref (fragment_bytes);
-      g_bytes_unref (vertex_bytes);
-
-      return;
-    }
-
-  if (kind == GSK_NGL_COMPILER_FRAGMENT)
-    loc = &self->fragment_source;
-  else if (kind == GSK_NGL_COMPILER_VERTEX)
-    loc = &self->vertex_source;
-  else
-    g_return_if_reached ();
-
-  if (*loc != source_bytes)
-    {
-      g_clear_pointer (loc, g_bytes_unref);
-      *loc = g_bytes_ref (source_bytes);
-    }
-}
-
-void
-gsk_ngl_compiler_set_source_from_resource (GskNglCompiler     *self,
-                                           GskNglCompilerKind  kind,
-                                           const char         *resource_path)
-{
-  GError *error = NULL;
-  GBytes *bytes;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
-                    kind == GSK_NGL_COMPILER_VERTEX ||
-                    kind == GSK_NGL_COMPILER_FRAGMENT);
-  g_return_if_fail (resource_path != NULL);
-
-  bytes = g_resources_lookup_data (resource_path,
-                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
-                                   &error);
-
-  if (bytes == NULL)
-    g_warning ("Cannot set shader from resource: %s", error->message);
-  else
-    gsk_ngl_compiler_set_source (self, kind, bytes);
-
-  g_clear_pointer (&bytes, g_bytes_unref);
-  g_clear_error (&error);
-}
-
-void
-gsk_ngl_compiler_set_suffix (GskNglCompiler     *self,
-                             GskNglCompilerKind  kind,
-                             GBytes             *suffix_bytes)
-{
-  GBytes **loc;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
-                    kind == GSK_NGL_COMPILER_FRAGMENT);
-  g_return_if_fail (suffix_bytes != NULL);
-
-  if (suffix_bytes == NULL)
-    suffix_bytes = empty_bytes;
-
-  if (kind == GSK_NGL_COMPILER_FRAGMENT)
-    loc = &self->fragment_suffix;
-  else if (kind == GSK_NGL_COMPILER_VERTEX)
-    loc = &self->vertex_suffix;
-  else
-    g_return_if_reached ();
-
-  if (*loc != suffix_bytes)
-    {
-      g_clear_pointer (loc, g_bytes_unref);
-      *loc = g_bytes_ref (suffix_bytes);
-    }
-}
-
-void
-gsk_ngl_compiler_set_suffix_from_resource (GskNglCompiler     *self,
-                                           GskNglCompilerKind  kind,
-                                           const char         *resource_path)
-{
-  GError *error = NULL;
-  GBytes *bytes;
-
-  g_return_if_fail (GSK_IS_NGL_COMPILER (self));
-  g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
-                    kind == GSK_NGL_COMPILER_FRAGMENT);
-  g_return_if_fail (resource_path != NULL);
-
-  bytes = g_resources_lookup_data (resource_path,
-                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
-                                   &error);
-
-  if (bytes == NULL)
-    g_warning ("Cannot set suffix from resource: %s", error->message);
-  else
-    gsk_ngl_compiler_set_suffix (self, kind, bytes);
-
-  g_clear_pointer (&bytes, g_bytes_unref);
-  g_clear_error (&error);
-}
-
-static void
-prepend_line_numbers (char    *code,
-                      GString *s)
-{
-  char *p;
-  int line;
-
-  p = code;
-  line = 1;
-  while (*p)
-    {
-      char *end = strchr (p, '\n');
-      if (end)
-        end = end + 1; /* Include newline */
-      else
-        end = p + strlen (p);
-
-      g_string_append_printf (s, "%3d| ", line++);
-      g_string_append_len (s, p, end - p);
-
-      p = end;
-    }
-}
-
-static gboolean
-check_shader_error (int      shader_id,
-                    GError **error)
-{
-  GLint status;
-  GLint log_len;
-  GLint code_len;
-  char *buffer;
-  char *code;
-  GString *s;
-
-  glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status);
-
-  if G_LIKELY (status == GL_TRUE)
-    return TRUE;
-
-  glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len);
-  buffer = g_malloc0 (log_len + 1);
-  glGetShaderInfoLog (shader_id, log_len, NULL, buffer);
-
-  glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
-  code = g_malloc0 (code_len + 1);
-  glGetShaderSource (shader_id, code_len, NULL, code);
-
-  s = g_string_new ("");
-  prepend_line_numbers (code, s);
-
-  g_set_error (error,
-               GDK_GL_ERROR,
-               GDK_GL_ERROR_COMPILATION_FAILED,
-               "Compilation failure in shader.\n"
-               "Source Code: %s\n"
-               "\n"
-               "Error Message:\n"
-               "%s\n"
-               "\n",
-               s->str,
-               buffer);
-
-  g_string_free (s, TRUE);
-  g_free (buffer);
-  g_free (code);
-
-  return FALSE;
-}
-
-static void
-print_shader_info (const char *prefix,
-                   int         shader_id,
-                   const char *name)
-{
-  if (GSK_DEBUG_CHECK(SHADERS))
-    {
-      int code_len;
-
-      glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
-
-      if (code_len > 0)
-        {
-          char *code;
-          GString *s;
-
-          code = g_malloc0 (code_len + 1);
-          glGetShaderSource (shader_id, code_len, NULL, code);
-
-          s = g_string_new (NULL);
-          prepend_line_numbers (code, s);
-
-          g_message ("%s %d, %s:\n%s",
-                     prefix, shader_id,
-                     name ? name : "unnamed",
-                     s->str);
-          g_string_free (s,  TRUE);
-          g_free (code);
-        }
-    }
-}
-
-static const char *
-get_shader_string (GBytes *bytes)
-{
-  /* 0 length bytes will give us NULL back */
-  const char *str = g_bytes_get_data (bytes, NULL);
-  return str ? str : "";
-}
-
-GskNglProgram *
-gsk_ngl_compiler_compile (GskNglCompiler  *self,
-                          const char      *name,
-                          const char      *clip,
-                          GError         **error)
-{
-  char version[32];
-  const char *debug = "";
-  const char *legacy = "";
-  const char *gl3 = "";
-  const char *gles = "";
-  int program_id;
-  int vertex_id;
-  int fragment_id;
-  int status;
-
-  g_return_val_if_fail (GSK_IS_NGL_COMPILER (self), NULL);
-  g_return_val_if_fail (self->all_preamble != NULL, NULL);
-  g_return_val_if_fail (self->fragment_preamble != NULL, NULL);
-  g_return_val_if_fail (self->vertex_preamble != NULL, NULL);
-  g_return_val_if_fail (self->fragment_source != NULL, NULL);
-  g_return_val_if_fail (self->vertex_source != NULL, NULL);
-  g_return_val_if_fail (self->driver != NULL, NULL);
-
-  gsk_ngl_command_queue_make_current (self->driver->command_queue);
-
-  g_snprintf (version, sizeof version, "#version %d\n", self->glsl_version);
-
-  if (self->debug_shaders)
-    debug = "#define GSK_DEBUG 1\n";
-
-  if (self->legacy)
-    legacy = "#define GSK_LEGACY 1\n";
-
-  if (self->gles)
-    gles = "#define GSK_GLES 1\n";
-
-  if (self->gl3)
-    gl3 = "#define GSK_GL3 1\n";
-
-  vertex_id = glCreateShader (GL_VERTEX_SHADER);
-  glShaderSource (vertex_id,
-                  10,
-                  (const char *[]) {
-                    version, debug, legacy, gl3, gles,
-                    clip,
-                    get_shader_string (self->all_preamble),
-                    get_shader_string (self->vertex_preamble),
-                    get_shader_string (self->vertex_source),
-                    get_shader_string (self->vertex_suffix),
-                  },
-                  (int[]) {
-                    strlen (version),
-                    strlen (debug),
-                    strlen (legacy),
-                    strlen (gl3),
-                    strlen (gles),
-                    strlen (clip),
-                    g_bytes_get_size (self->all_preamble),
-                    g_bytes_get_size (self->vertex_preamble),
-                    g_bytes_get_size (self->vertex_source),
-                    g_bytes_get_size (self->vertex_suffix),
-                  });
-  glCompileShader (vertex_id);
-
-  if (!check_shader_error (vertex_id, error))
-    {
-      glDeleteShader (vertex_id);
-      return NULL;
-    }
-
-  print_shader_info ("Vertex shader", vertex_id, name);
-
-  fragment_id = glCreateShader (GL_FRAGMENT_SHADER);
-  glShaderSource (fragment_id,
-                  10,
-                  (const char *[]) {
-                    version, debug, legacy, gl3, gles,
-                    clip,
-                    get_shader_string (self->all_preamble),
-                    get_shader_string (self->fragment_preamble),
-                    get_shader_string (self->fragment_source),
-                    get_shader_string (self->fragment_suffix),
-                  },
-                  (int[]) {
-                    strlen (version),
-                    strlen (debug),
-                    strlen (legacy),
-                    strlen (gl3),
-                    strlen (gles),
-                    strlen (clip),
-                    g_bytes_get_size (self->all_preamble),
-                    g_bytes_get_size (self->fragment_preamble),
-                    g_bytes_get_size (self->fragment_source),
-                    g_bytes_get_size (self->fragment_suffix),
-                  });
-  glCompileShader (fragment_id);
-
-  if (!check_shader_error (fragment_id, error))
-    {
-      glDeleteShader (vertex_id);
-      glDeleteShader (fragment_id);
-      return NULL;
-    }
-
-  print_shader_info ("Fragment shader", fragment_id, name);
-
-  program_id = glCreateProgram ();
-  glAttachShader (program_id, vertex_id);
-  glAttachShader (program_id, fragment_id);
-
-  for (guint i = 0; i < self->attrib_locations->len; i++)
-    {
-      const GskNglProgramAttrib *attrib;
-
-      attrib = &g_array_index (self->attrib_locations, GskNglProgramAttrib, i);
-      glBindAttribLocation (program_id, attrib->location, attrib->name);
-    }
-
-  glLinkProgram (program_id);
-
-  glGetProgramiv (program_id, GL_LINK_STATUS, &status);
-
-  glDetachShader (program_id, vertex_id);
-  glDeleteShader (vertex_id);
-
-  glDetachShader (program_id, fragment_id);
-  glDeleteShader (fragment_id);
-
-  if (status == GL_FALSE)
-    {
-      char *buffer = NULL;
-      int log_len = 0;
-
-      glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len);
-
-      if (log_len > 0)
-        {
-          /* log_len includes NULL */
-          buffer = g_malloc0 (log_len);
-          glGetProgramInfoLog (program_id, log_len, NULL, buffer);
-        }
-
-      g_warning ("Linking failure in shader:\n%s",
-                 buffer ? buffer : "");
-
-      g_set_error (error,
-                   GDK_GL_ERROR,
-                   GDK_GL_ERROR_LINK_FAILED,
-                   "Linking failure in shader: %s",
-                   buffer ? buffer : "");
-
-      g_free (buffer);
-
-      glDeleteProgram (program_id);
-
-      return NULL;
-    }
-
-  return gsk_ngl_program_new (self->driver, name, program_id);
-}
diff --git a/gsk/ngl/gsknglcompilerprivate.h b/gsk/ngl/gsknglcompilerprivate.h
deleted file mode 100644 (file)
index a2cfa2f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/* gsknglcompilerprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_COMPILER_PRIVATE_H__
-#define __GSK_NGL_COMPILER_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-
-G_BEGIN_DECLS
-
-typedef enum _GskNglCompilerKind
-{
-  GSK_NGL_COMPILER_ALL,
-  GSK_NGL_COMPILER_FRAGMENT,
-  GSK_NGL_COMPILER_VERTEX,
-} GskNglCompilerKind;
-
-#define GSK_TYPE_GL_COMPILER (gsk_ngl_compiler_get_type())
-
-G_DECLARE_FINAL_TYPE (GskNglCompiler, gsk_ngl_compiler, GSK, NGL_COMPILER, GObject)
-
-GskNglCompiler *gsk_ngl_compiler_new                        (GskNglDriver        *driver,
-                                                             gboolean             debug);
-void            gsk_ngl_compiler_set_preamble               (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             GBytes              *preamble_bytes);
-void            gsk_ngl_compiler_set_preamble_from_resource (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             const char          *resource_path);
-void            gsk_ngl_compiler_set_source                 (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             GBytes              *source_bytes);
-void            gsk_ngl_compiler_set_source_from_resource   (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             const char          *resource_path);
-void            gsk_ngl_compiler_set_suffix                 (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             GBytes              *suffix_bytes);
-void            gsk_ngl_compiler_set_suffix_from_resource   (GskNglCompiler      *self,
-                                                             GskNglCompilerKind   kind,
-                                                             const char          *resource_path);
-void            gsk_ngl_compiler_bind_attribute             (GskNglCompiler      *self,
-                                                             const char          *name,
-                                                             guint                location);
-void            gsk_ngl_compiler_clear_attributes           (GskNglCompiler      *self);
-GskNglProgram  *gsk_ngl_compiler_compile                    (GskNglCompiler      *self,
-                                                             const char          *name,
-                                                             const char          *clip,
-                                                             GError             **error);
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_COMPILER_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngldriver.c b/gsk/ngl/gskngldriver.c
deleted file mode 100644 (file)
index 87f2864..0000000
+++ /dev/null
@@ -1,1386 +0,0 @@
-/* gskngldriver.c
- *
- * Copyright 2017 Timm Bäder <mail@baedert.org>
- * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
- * Copyright 2018 Alexander Larsson <alexl@redhat.com>
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdkdisplayprivate.h>
-#include <gdk/gdktextureprivate.h>
-#include <gdk/gdkprofilerprivate.h>
-#include <gsk/gskdebugprivate.h>
-#include <gsk/gskglshaderprivate.h>
-#include <gsk/gskrendererprivate.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gsknglcompilerprivate.h"
-#include "gskngldriverprivate.h"
-#include "gsknglglyphlibraryprivate.h"
-#include "gskngliconlibraryprivate.h"
-#include "gsknglprogramprivate.h"
-#include "gsknglshadowlibraryprivate.h"
-#include "gskngltextureprivate.h"
-#include "fp16private.h"
-
-#define ATLAS_SIZE 512
-#define MAX_OLD_RATIO 0.5
-
-G_DEFINE_TYPE (GskNglDriver, gsk_ngl_driver, G_TYPE_OBJECT)
-
-static guint
-texture_key_hash (gconstpointer v)
-{
-  const GskTextureKey *k = (const GskTextureKey *)v;
-
-  /* Optimize for 0..3 where 0 is the scaled out case. Usually
-   * we'll be squarely on 1 or 2 for standard vs HiDPI. When rendering
-   * to a texture scaled out like in node-editor, we might be < 1.
-   */
-  guint scale_x = floorf (k->scale_x);
-  guint scale_y = floorf (k->scale_y);
-
-  return GPOINTER_TO_SIZE (k->pointer) ^
-    ((scale_x << 8) |
-     (scale_y << 6) |
-     (k->filter << 1) |
-     k->pointer_is_child);
-}
-
-static gboolean
-texture_key_equal (gconstpointer v1,
-                   gconstpointer v2)
-{
-  const GskTextureKey *k1 = (const GskTextureKey *)v1;
-  const GskTextureKey *k2 = (const GskTextureKey *)v2;
-
-  return k1->pointer == k2->pointer &&
-         k1->scale_x == k2->scale_x &&
-         k1->scale_y == k2->scale_y &&
-         k1->filter == k2->filter &&
-         k1->pointer_is_child == k2->pointer_is_child &&
-         (!k1->pointer_is_child || memcmp (&k1->parent_rect, &k2->parent_rect, sizeof k1->parent_rect) == 0);
-}
-
-static void
-remove_texture_key_for_id (GskNglDriver *self,
-                           guint         texture_id)
-{
-  GskTextureKey *key;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (texture_id > 0);
-
-  /* g_hash_table_remove() will cause @key to be freed */
-  if (g_hash_table_steal_extended (self->texture_id_to_key,
-                                   GUINT_TO_POINTER (texture_id),
-                                   NULL,
-                                   (gpointer *)&key))
-    g_hash_table_remove (self->key_to_texture_id, key);
-}
-
-static void
-gsk_ngl_texture_destroyed (gpointer data)
-{
-  ((GskNglTexture *)data)->user = NULL;
-}
-
-static void
-gsk_ngl_driver_autorelease_texture (GskNglDriver *self,
-                                    guint         texture_id)
-{
-  g_assert (GSK_IS_NGL_DRIVER (self));
-
-  g_array_append_val (self->texture_pool, texture_id);
-}
-
-static guint
-gsk_ngl_driver_collect_unused_textures (GskNglDriver *self,
-                                        gint64        watermark)
-{
-  GHashTableIter iter;
-  gpointer k, v;
-  guint old_size;
-  guint collected;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-
-  old_size = g_hash_table_size (self->textures);
-
-  g_hash_table_iter_init (&iter, self->textures);
-  while (g_hash_table_iter_next (&iter, &k, &v))
-    {
-      GskNglTexture *t = v;
-
-      if (t->user || t->permanent)
-        continue;
-
-      if (t->last_used_in_frame <= watermark)
-        {
-          g_hash_table_iter_steal (&iter);
-
-          g_assert (t->link.prev == NULL);
-          g_assert (t->link.next == NULL);
-          g_assert (t->link.data == t);
-
-          remove_texture_key_for_id (self, t->texture_id);
-          gsk_ngl_driver_autorelease_texture (self, t->texture_id);
-          t->texture_id = 0;
-          gsk_ngl_texture_free (t);
-        }
-    }
-
-  collected = old_size - g_hash_table_size (self->textures);
-
-  return collected;
-}
-
-static void
-gsk_ngl_texture_atlas_free (GskNglTextureAtlas *atlas)
-{
-  if (atlas->texture_id != 0)
-    {
-      glDeleteTextures (1, &atlas->texture_id);
-      atlas->texture_id = 0;
-    }
-
-  g_clear_pointer (&atlas->nodes, g_free);
-  g_slice_free (GskNglTextureAtlas, atlas);
-}
-
-GskNglTextureAtlas *
-gsk_ngl_driver_create_atlas (GskNglDriver *self)
-{
-  GskNglTextureAtlas *atlas;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-
-  atlas = g_slice_new0 (GskNglTextureAtlas);
-  atlas->width = ATLAS_SIZE;
-  atlas->height = ATLAS_SIZE;
-  /* TODO: We might want to change the strategy about the amount of
-   *       nodes here? stb_rect_pack.h says width is optimal. */
-  atlas->nodes = g_malloc0_n (atlas->width, sizeof (struct stbrp_node));
-  stbrp_init_target (&atlas->context, atlas->width, atlas->height, atlas->nodes, atlas->width);
-  atlas->texture_id = gsk_ngl_command_queue_create_texture (self->command_queue,
-                                                            atlas->width,
-                                                            atlas->height,
-                                                            GL_RGBA8,
-                                                            GL_LINEAR,
-                                                            GL_LINEAR);
-
-  gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
-                                      GL_TEXTURE, atlas->texture_id,
-                                      "Texture atlas %d",
-                                      atlas->texture_id);
-
-  g_ptr_array_add (self->atlases, atlas);
-
-  return atlas;
-}
-
-static void
-remove_program (gpointer data)
-{
-  GskNglProgram *program = data;
-
-  g_assert (!program || GSK_IS_NGL_PROGRAM (program));
-
-  if (program != NULL)
-    {
-      gsk_ngl_program_delete (program);
-      g_object_unref (program);
-    }
-}
-
-static void
-gsk_ngl_driver_shader_weak_cb (gpointer  data,
-                               GObject  *where_object_was)
-{
-  GskNglDriver *self = data;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-
-  if (self->shader_cache != NULL)
-    g_hash_table_remove (self->shader_cache, where_object_was);
-}
-
-static void
-gsk_ngl_driver_dispose (GObject *object)
-{
-  GskNglDriver *self = (GskNglDriver *)object;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (self->in_frame == FALSE);
-
-#define GSK_NGL_NO_UNIFORMS
-#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)
-#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) \
-  GSK_NGL_DELETE_PROGRAM(name);                          \
-  GSK_NGL_DELETE_PROGRAM(name ## _no_clip);              \
-  GSK_NGL_DELETE_PROGRAM(name ## _rect_clip);
-#define GSK_NGL_DELETE_PROGRAM(name)                     \
-  G_STMT_START {                                         \
-    if (self->name)                                      \
-      gsk_ngl_program_delete (self->name);               \
-    g_clear_object (&self->name);                        \
-  } G_STMT_END;
-# include "gsknglprograms.defs"
-#undef GSK_NGL_NO_UNIFORMS
-#undef GSK_NGL_ADD_UNIFORM
-#undef GSK_NGL_DEFINE_PROGRAM
-
-  if (self->shader_cache != NULL)
-    {
-      GHashTableIter iter;
-      gpointer k, v;
-
-      g_hash_table_iter_init (&iter, self->shader_cache);
-      while (g_hash_table_iter_next (&iter, &k, &v))
-        {
-          GskGLShader *shader = k;
-          g_object_weak_unref (G_OBJECT (shader),
-                               gsk_ngl_driver_shader_weak_cb,
-                               self);
-          g_hash_table_iter_remove (&iter);
-        }
-
-      g_clear_pointer (&self->shader_cache, g_hash_table_unref);
-    }
-
-  if (self->command_queue != NULL)
-    {
-      gsk_ngl_command_queue_make_current (self->command_queue);
-      gsk_ngl_driver_collect_unused_textures (self, 0);
-      g_clear_object (&self->command_queue);
-    }
-
-  if (self->autorelease_framebuffers->len > 0)
-    {
-      glDeleteFramebuffers (self->autorelease_framebuffers->len,
-                            (GLuint *)(gpointer)self->autorelease_framebuffers->data);
-      self->autorelease_framebuffers->len = 0;
-    }
-
-  g_clear_pointer (&self->texture_pool, g_array_unref);
-
-  g_assert (!self->textures || g_hash_table_size (self->textures) == 0);
-  g_assert (!self->texture_id_to_key || g_hash_table_size (self->texture_id_to_key) == 0);
-  g_assert (!self->key_to_texture_id|| g_hash_table_size (self->key_to_texture_id) == 0);
-
-  g_clear_object (&self->glyphs);
-  g_clear_object (&self->icons);
-  g_clear_object (&self->shadows);
-
-  g_clear_pointer (&self->atlases, g_ptr_array_unref);
-  g_clear_pointer (&self->autorelease_framebuffers, g_array_unref);
-  g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
-  g_clear_pointer (&self->textures, g_hash_table_unref);
-  g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref);
-  g_clear_pointer (&self->texture_id_to_key, g_hash_table_unref);
-  g_clear_pointer (&self->render_targets, g_ptr_array_unref);
-  g_clear_pointer (&self->shader_cache, g_hash_table_unref);
-
-  g_clear_object (&self->command_queue);
-  g_clear_object (&self->shared_command_queue);
-
-  G_OBJECT_CLASS (gsk_ngl_driver_parent_class)->dispose (object);
-}
-
-static void
-gsk_ngl_driver_class_init (GskNglDriverClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->dispose = gsk_ngl_driver_dispose;
-}
-
-static void
-gsk_ngl_driver_init (GskNglDriver *self)
-{
-  self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (guint));
-  self->textures = g_hash_table_new_full (NULL, NULL, NULL,
-                                          (GDestroyNotify)gsk_ngl_texture_free);
-  self->texture_id_to_key = g_hash_table_new (NULL, NULL);
-  self->key_to_texture_id = g_hash_table_new_full (texture_key_hash,
-                                                   texture_key_equal,
-                                                   g_free,
-                                                   NULL);
-  self->shader_cache = g_hash_table_new_full (NULL, NULL, NULL, remove_program);
-  self->texture_pool = g_array_new (FALSE, FALSE, sizeof (guint));
-  self->render_targets = g_ptr_array_new ();
-  self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_ngl_texture_atlas_free);
-}
-
-static gboolean
-gsk_ngl_driver_load_programs (GskNglDriver  *self,
-                              GError       **error)
-{
-  GskNglCompiler *compiler;
-  gboolean ret = FALSE;
-  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue));
-
-  compiler = gsk_ngl_compiler_new (self, self->debug);
-
-  /* Setup preambles that are shared by all shaders */
-  gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                               GSK_NGL_COMPILER_ALL,
-                                               "/org/gtk/libgsk/ngl/preamble.glsl");
-  gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                               GSK_NGL_COMPILER_VERTEX,
-                                               "/org/gtk/libgsk/ngl/preamble.vs.glsl");
-  gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                               GSK_NGL_COMPILER_FRAGMENT,
-                                               "/org/gtk/libgsk/ngl/preamble.fs.glsl");
-
-  /* Setup attributes that are provided via VBO */
-  gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0);
-  gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1);
-  gsk_ngl_compiler_bind_attribute (compiler, "aColor", 2);
-  gsk_ngl_compiler_bind_attribute (compiler, "aColor2", 3);
-
-  /* Use XMacros to register all of our programs and their uniforms */
-#define GSK_NGL_NO_UNIFORMS
-#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)                                                      \
-  gsk_ngl_program_add_uniform (program, #name, UNIFORM_##KEY);
-#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms)                                         \
-  gsk_ngl_compiler_set_source_from_resource (compiler, GSK_NGL_COMPILER_ALL, resource);          \
-  GSK_NGL_COMPILE_PROGRAM(name ## _no_clip, uniforms, "#define NO_CLIP 1\n");                    \
-  GSK_NGL_COMPILE_PROGRAM(name ## _rect_clip, uniforms, "#define RECT_CLIP 1\n");                \
-  GSK_NGL_COMPILE_PROGRAM(name, uniforms, "");
-#define GSK_NGL_COMPILE_PROGRAM(name, uniforms, clip)                                            \
-  G_STMT_START {                                                                                 \
-    GskNglProgram *program;                                                                      \
-    gboolean have_alpha;                                                                         \
-    gboolean have_source;                                                                        \
-                                                                                                 \
-    if (!(program = gsk_ngl_compiler_compile (compiler, #name, clip, error)))                    \
-      goto failure;                                                                              \
-                                                                                                 \
-    have_alpha = gsk_ngl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);         \
-    have_source = gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);      \
-    gsk_ngl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);              \
-    gsk_ngl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);                \
-    gsk_ngl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);            \
-    gsk_ngl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);              \
-                                                                                                 \
-    uniforms                                                                                     \
-                                                                                                 \
-    gsk_ngl_program_uniforms_added (program, have_source);                                       \
-    if (have_alpha)                                                                              \
-      gsk_ngl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f);                    \
-                                                                                                 \
-    *(GskNglProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskNglDriver, name)) =               \
-         g_steal_pointer (&program);                                                             \
-  } G_STMT_END;
-# include "gsknglprograms.defs"
-#undef GSK_NGL_DEFINE_PROGRAM_CLIP
-#undef GSK_NGL_DEFINE_PROGRAM
-#undef GSK_NGL_ADD_UNIFORM
-
-  ret = TRUE;
-
-failure:
-  g_clear_object (&compiler);
-
-  gdk_profiler_end_mark (start_time, "load programs", NULL);
-
-  return ret;
-}
-
-/**
- * gsk_ngl_driver_autorelease_framebuffer:
- * @self: a `GskNglDriver`
- * @framebuffer_id: the id of the OpenGL framebuffer
- *
- * Marks @framebuffer_id to be deleted when the current frame has cmopleted.
- */
-static void
-gsk_ngl_driver_autorelease_framebuffer (GskNglDriver *self,
-                                        guint         framebuffer_id)
-{
-  g_assert (GSK_IS_NGL_DRIVER (self));
-
-  g_array_append_val (self->autorelease_framebuffers, framebuffer_id);
-}
-
-static GskNglDriver *
-gsk_ngl_driver_new (GskNglCommandQueue  *command_queue,
-                    gboolean             debug_shaders,
-                    GError             **error)
-{
-  GskNglDriver *self;
-  GdkGLContext *context;
-  gint64 before G_GNUC_UNUSED;
-
-  g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (command_queue), NULL);
-
-  before = GDK_PROFILER_CURRENT_TIME;
-
-  context = gsk_ngl_command_queue_get_context (command_queue);
-
-  gdk_gl_context_make_current (context);
-
-  self = g_object_new (GSK_TYPE_NGL_DRIVER, NULL);
-  self->command_queue = g_object_ref (command_queue);
-  self->shared_command_queue = g_object_ref (command_queue);
-  self->debug = !!debug_shaders;
-
-  if (!gsk_ngl_driver_load_programs (self, error))
-    {
-      g_object_unref (self);
-      return NULL;
-    }
-
-  self->glyphs = gsk_ngl_glyph_library_new (self);
-  self->icons = gsk_ngl_icon_library_new (self);
-  self->shadows = gsk_ngl_shadow_library_new (self);
-
-  gdk_profiler_end_mark (before, "create GskNglDriver", NULL);
-
-  return g_steal_pointer (&self);
-}
-
-/**
- * gsk_ngl_driver_for_display:
- * @display: A #GdkDisplay that is known to support GL
- * @debug_shaders: if debug information for shaders should be displayed
- * @error: location for error information
- *
- * Retrieves a driver for a shared display. Generally this is shared across all GL
- * contexts for a display so that fewer programs are necessary for driving output.
- *
- * Returns: (transfer full): a `GskNglDriver` if successful; otherwise %NULL and
- *   @error is set.
- */
-GskNglDriver *
-gsk_ngl_driver_for_display (GdkDisplay  *display,
-                            gboolean     debug_shaders,
-                            GError     **error)
-{
-  GdkGLContext *context;
-  GskNglCommandQueue *command_queue = NULL;
-  GskNglDriver *driver;
-
-  g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL);
-
-  if ((driver = g_object_get_data (G_OBJECT (display), "GSK_NGL_DRIVER")))
-    return g_object_ref (driver);
-
-  context = gdk_display_get_gl_context (display);
-  g_assert (context);
-
-  gdk_gl_context_make_current (context);
-
-  /* Initially we create a command queue using the shared context. However,
-   * as frames are processed this will be replaced with the command queue
-   * for a given renderer. But since the programs are compiled into the
-   * shared context, all other contexts sharing with it will have access
-   * to those programs.
-   */
-  command_queue = gsk_ngl_command_queue_new (context, NULL);
-
-  if (!(driver = gsk_ngl_driver_new (command_queue, debug_shaders, error)))
-    goto failure;
-
-  g_object_set_data_full (G_OBJECT (display),
-                          "GSK_NGL_DRIVER",
-                          g_object_ref (driver),
-                          g_object_unref);
-
-failure:
-  g_clear_object (&command_queue);
-
-  return g_steal_pointer (&driver);
-}
-
-static GPtrArray *
-gsk_ngl_driver_compact_atlases (GskNglDriver *self)
-{
-  GPtrArray *removed = NULL;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-
-  for (guint i = self->atlases->len; i > 0; i--)
-    {
-      GskNglTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1);
-
-      if (gsk_ngl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
-        {
-          GSK_NOTE (GLYPH_CACHE,
-                    g_message ("Dropping atlas %d (%g.2%% old)", i,
-                               100.0 * gsk_ngl_texture_atlas_get_unused_ratio (atlas)));
-          if (removed == NULL)
-            removed = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_ngl_texture_atlas_free);
-          g_ptr_array_add (removed, g_ptr_array_steal_index (self->atlases, i - 1));
-        }
-    }
-
-  GSK_NOTE (GLYPH_CACHE, {
-    static guint timestamp;
-    if (timestamp++ % 60 == 0)
-      g_message ("%d atlases", self->atlases->len);
-  });
-
-  return removed;
-}
-
-/**
- * gsk_ngl_driver_begin_frame:
- * @self: a `GskNglDriver`
- * @command_queue: A `GskNglCommandQueue` from the renderer
- *
- * Begin a new frame.
- *
- * Texture atlases, pools, and other resources will be prepared to draw the
- * next frame. The command queue should be one that was created for the
- * target context to be drawn into (the context of the renderer's surface).
- */
-void
-gsk_ngl_driver_begin_frame (GskNglDriver       *self,
-                            GskNglCommandQueue *command_queue)
-{
-  gint64 last_frame_id;
-  GPtrArray *removed;
-
-  g_return_if_fail (GSK_IS_NGL_DRIVER (self));
-  g_return_if_fail (GSK_IS_NGL_COMMAND_QUEUE (command_queue));
-  g_return_if_fail (self->in_frame == FALSE);
-
-  last_frame_id = self->current_frame_id;
-
-  self->in_frame = TRUE;
-  self->current_frame_id++;
-
-  g_set_object (&self->command_queue, command_queue);
-
-  gsk_ngl_command_queue_begin_frame (self->command_queue);
-
-  /* Compact atlases with too many freed pixels */
-  removed = gsk_ngl_driver_compact_atlases (self);
-
-  /* Mark unused pixel regions of the atlases */
-  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons),
-                                       self->current_frame_id,
-                                       removed);
-  gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs),
-                                       self->current_frame_id,
-                                       removed);
-
-  /* Cleanup old shadows */
-  gsk_ngl_shadow_library_begin_frame (self->shadows);
-
-  /* Remove all textures that are from a previous frame or are no
-   * longer used by linked GdkTexture. We do this at the beginning
-   * of the following frame instead of the end so that we reduce chances
-   * we block on any resources while delivering our frames.
-   */
-  gsk_ngl_driver_collect_unused_textures (self, last_frame_id - 1);
-
-  /* Now free atlas textures */
-  g_clear_pointer (&removed, g_ptr_array_unref);
-}
-
-/**
- * gsk_ngl_driver_end_frame:
- * @self: a `GskNglDriver`
- *
- * Clean up resources from drawing the current frame.
- *
- * Temporary resources used while drawing will be released.
- */
-void
-gsk_ngl_driver_end_frame (GskNglDriver *self)
-{
-  g_return_if_fail (GSK_IS_NGL_DRIVER (self));
-  g_return_if_fail (self->in_frame == TRUE);
-
-  gsk_ngl_command_queue_make_current (self->command_queue);
-  gsk_ngl_command_queue_end_frame (self->command_queue);
-
-  self->in_frame = FALSE;
-}
-
-/**
- * gsk_ngl_driver_after_frame:
- * @self: a `GskNglDriver`
- *
- * This function does post-frame cleanup operations.
- *
- * To reduce the chances of blocking on the driver it is performed
- * after the frame has swapped buffers.
- */
-void
-gsk_ngl_driver_after_frame (GskNglDriver *self)
-{
-  g_return_if_fail (GSK_IS_NGL_DRIVER (self));
-  g_return_if_fail (self->in_frame == FALSE);
-
-  /* Release any render targets (possibly adding them to
-   * self->autorelease_framebuffers) so we can release the FBOs immediately
-   * afterwards.
-   */
-  while (self->render_targets->len > 0)
-    {
-      GskNglRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1);
-
-      gsk_ngl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
-      gsk_ngl_driver_autorelease_texture (self, render_target->texture_id);
-      g_slice_free (GskNglRenderTarget, render_target);
-
-      self->render_targets->len--;
-    }
-
-  /* Now that we have collected render targets, release all the FBOs */
-  if (self->autorelease_framebuffers->len > 0)
-    {
-      glDeleteFramebuffers (self->autorelease_framebuffers->len,
-                            (GLuint *)(gpointer)self->autorelease_framebuffers->data);
-      self->autorelease_framebuffers->len = 0;
-    }
-
-  /* Release any cached textures we used during the frame */
-  if (self->texture_pool->len > 0)
-    {
-      glDeleteTextures (self->texture_pool->len,
-                        (GLuint *)(gpointer)self->texture_pool->data);
-      self->texture_pool->len = 0;
-    }
-
-  /* Reset command queue to our shared queue incase we have operations
-   * that need to be processed outside of a frame (such as callbacks
-   * from external systems such as GDK).
-   */
-  g_set_object (&self->command_queue, self->shared_command_queue);
-}
-
-GdkGLContext *
-gsk_ngl_driver_get_context (GskNglDriver *self)
-{
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-  g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue), NULL);
-
-  return gsk_ngl_command_queue_get_context (self->command_queue);
-}
-
-/**
- * gsk_ngl_driver_cache_texture:
- * @self: a `GskNglDriver`
- * @key: the key for the texture
- * @texture_id: the id of the texture to be cached
- *
- * Inserts @texture_id into the texture cache using @key.
- *
- * Textures can be looked up by @key after calling this function using
- * gsk_ngl_driver_lookup_texture().
- *
- * Textures that have not been used within a number of frames will be
- * purged from the texture cache automatically.
- */
-void
-gsk_ngl_driver_cache_texture (GskNglDriver        *self,
-                              const GskTextureKey *key,
-                              guint                texture_id)
-{
-  GskTextureKey *k;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (key != NULL);
-  g_assert (texture_id > 0);
-  g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id)));
-
-  k = g_memdup (key, sizeof *key);
-
-  g_hash_table_insert (self->key_to_texture_id, k, GUINT_TO_POINTER (texture_id));
-  g_hash_table_insert (self->texture_id_to_key, GUINT_TO_POINTER (texture_id), k);
-}
-
-/**
- * gsk_ngl_driver_load_texture:
- * @self: a `GdkTexture`
- * @texture: a `GdkTexture`
- * @min_filter: GL_NEAREST or GL_LINEAR
- * @mag_filter: GL_NEAREST or GL_LINEAR
- *
- * Loads a `GdkTexture` by uploading the contents to the GPU when
- * necessary. If @texture is a `GdkGLTexture`, it can be used without
- * uploading contents to the GPU.
- *
- * If the texture has already been uploaded and not yet released
- * from cache, this function returns that texture id without further
- * work.
- *
- * If the texture has not been used for a number of frames, it will
- * be removed from cache.
- *
- * There is no need to release the resulting texture identifier after
- * using it. It will be released automatically.
- *
- * Returns: a texture identifier
- */
-guint
-gsk_ngl_driver_load_texture (GskNglDriver *self,
-                             GdkTexture   *texture,
-                             int           min_filter,
-                             int           mag_filter)
-{
-  GdkGLContext *context;
-  GdkTexture *downloaded_texture;
-  GskNglTexture *t;
-  guint texture_id;
-  int height;
-  int width;
-  int format;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), 0);
-  g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
-  g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue), 0);
-
-  context = self->command_queue->context;
-
-  format = GL_RGBA8;
-
-  if (GDK_IS_GL_TEXTURE (texture))
-    {
-      GdkGLTexture *gl_texture = (GdkGLTexture *) texture;
-      GdkGLContext *texture_context = gdk_gl_texture_get_context (gl_texture);
-
-      if (gdk_gl_context_is_shared (context, texture_context))
-        {
-          /* A GL texture from the same GL context is a simple task... */
-          return gdk_gl_texture_get_id (gl_texture);
-        }
-      else
-        {
-          downloaded_texture = gdk_texture_download_texture (texture);
-        }
-    }
-  else
-    {
-      if ((t = gdk_texture_get_render_data (texture, self)))
-        {
-          if (t->min_filter == min_filter && t->mag_filter == mag_filter)
-            return t->texture_id;
-        }
-
-      downloaded_texture = gdk_texture_download_texture (texture);
-    }
-
-  /* The download_texture() call may have switched the GL context. Make sure
-   * the right context is at work again. */
-  gdk_gl_context_make_current (context);
-
-  width = gdk_texture_get_width (texture);
-  height = gdk_texture_get_height (texture);
-  texture_id = gsk_ngl_command_queue_upload_texture (self->command_queue,
-                                                     downloaded_texture,
-                                                     0,
-                                                     0,
-                                                     width,
-                                                     height,
-                                                     min_filter,
-                                                     mag_filter);
-
-  t = gsk_ngl_texture_new (texture_id,
-                           width, height, format, min_filter, mag_filter,
-                           self->current_frame_id);
-
-  g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t);
-
-  if (gdk_texture_set_render_data (texture, self, t, gsk_ngl_texture_destroyed))
-    t->user = texture;
-
-  gdk_gl_context_label_object_printf (context, GL_TEXTURE, t->texture_id,
-                                      "GdkTexture<%p> %d", texture, t->texture_id);
-
-  g_clear_object (&downloaded_texture);
-
-  return texture_id;
-}
-
-/**
- * gsk_ngl_driver_create_texture:
- * @self: a `GskNglDriver`
- * @width: the width of the texture
- * @height: the height of the texture
- * @format: format for the texture
- * @min_filter: GL_NEAREST or GL_LINEAR
- * @mag_filter: GL_NEAREST or GL_FILTER
- *
- * Creates a new texture immediately that can be used by the caller
- * to upload data, map to a framebuffer, or other uses which may
- * modify the texture immediately.
- *
- * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F.
- *
- * Use gsk_ngl_driver_release_texture() to release this texture back into
- * the pool so it may be reused later in the pipeline.
- *
- * Returns: a `GskNglTexture` which can be returned to the pool with
- *   gsk_ngl_driver_release_texture().
- */
-GskNglTexture *
-gsk_ngl_driver_create_texture (GskNglDriver *self,
-                               float         width,
-                               float         height,
-                               int           format,
-                               int           min_filter,
-                               int           mag_filter)
-{
-  GskNglTexture *texture;
-  guint texture_id;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-
-  texture_id = gsk_ngl_command_queue_create_texture (self->command_queue,
-                                                     width, height,
-                                                     format,
-                                                     min_filter, mag_filter);
-  texture = gsk_ngl_texture_new (texture_id,
-                                 width, height,
-                                 format,
-                                 min_filter, mag_filter,
-                                 self->current_frame_id);
-  g_hash_table_insert (self->textures,
-                       GUINT_TO_POINTER (texture->texture_id),
-                       texture);
-
-  return texture;
-}
-
-/**
- * gsk_ngl_driver_release_texture:
- * @self: a `GskNglDriver`
- * @texture: a `GskNglTexture`
- *
- * Releases @texture back into the pool so that it can be used later
- * in the command stream by future batches. This helps reduce VRAM
- * usage on the GPU.
- *
- * When the frame has completed, pooled textures will be released
- * to free additional VRAM back to the system.
- */
-void
-gsk_ngl_driver_release_texture (GskNglDriver  *self,
-                                GskNglTexture *texture)
-{
-  guint texture_id;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (texture != NULL);
-
-  texture_id = texture->texture_id;
-  texture->texture_id = 0;
-  gsk_ngl_texture_free (texture);
-
-  if (texture_id > 0)
-    remove_texture_key_for_id (self, texture_id);
-
-  g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
-  gsk_ngl_driver_autorelease_texture (self, texture_id);
-}
-
-/**
- * gsk_ngl_driver_create_render_target:
- * @self: a `GskNglDriver`
- * @width: the width for the render target
- * @height: the height for the render target
- * @format: the format to use
- * @min_filter: the min filter to use for the texture
- * @mag_filter: the mag filter to use for the texture
- * @out_render_target: (out): a location for the render target
- *
- * Creates a new render target which contains a framebuffer and a texture
- * bound to that framebuffer of the size @width x @height and using the
- * appropriate filters.
- *
- * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F.
- *
- * Use gsk_ngl_driver_release_render_target() when you are finished with
- * the render target to release it. You may steal the texture from the
- * render target when releasing it.
- *
- * Returns: %TRUE if successful; otherwise %FALSE and @out_fbo_id and
- *   @out_texture_id are undefined.
- */
-gboolean
-gsk_ngl_driver_create_render_target (GskNglDriver        *self,
-                                     int                  width,
-                                     int                  height,
-                                     int                  format,
-                                     int                  min_filter,
-                                     int                  mag_filter,
-                                     GskNglRenderTarget **out_render_target)
-{
-  guint framebuffer_id;
-  guint texture_id;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), FALSE);
-  g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue), FALSE);
-  g_return_val_if_fail (out_render_target != NULL, FALSE);
-
-#if 0
-  if (self->render_targets->len > 0)
-    {
-      for (guint i = self->render_targets->len; i > 0; i--)
-        {
-          GskNglRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1);
-
-          if (render_target->width == width &&
-              render_target->height == height &&
-              render_target->min_filter == min_filter &&
-              render_target->mag_filter == mag_filter)
-            {
-              *out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1);
-              return TRUE;
-            }
-        }
-    }
-#endif
-
-  if (gsk_ngl_command_queue_create_render_target (self->command_queue,
-                                                  width, height,
-                                                  format,
-                                                  min_filter, mag_filter,
-                                                  &framebuffer_id, &texture_id))
-    {
-      GskNglRenderTarget *render_target;
-
-      render_target = g_slice_new0 (GskNglRenderTarget);
-      render_target->min_filter = min_filter;
-      render_target->mag_filter = mag_filter;
-      render_target->format = format;
-      render_target->width = width;
-      render_target->height = height;
-      render_target->framebuffer_id = framebuffer_id;
-      render_target->texture_id = texture_id;
-
-      *out_render_target = render_target;
-
-      return TRUE;
-    }
-
-  *out_render_target = NULL;
-
-  return FALSE;
-}
-
-/**
- * gsk_ngl_driver_release_render_target:
- * @self: a `GskNglDriver`
- * @render_target: a `GskNglRenderTarget` created with
- *   gsk_ngl_driver_create_render_target().
- * @release_texture: if the texture should also be released
- *
- * Releases a render target that was previously created. An attempt may
- * be made to cache the render target so that future creations of render
- * targets are performed faster.
- *
- * If @release_texture is %FALSE, the backing texture id is returned and
- * the framebuffer is released. Otherwise, both the texture and framebuffer
- * are released or cached until the end of the frame.
- *
- * This may be called when building the render job as the texture or
- * framebuffer will not be removed immediately.
- *
- * Returns: a texture id if @release_texture is %FALSE, otherwise zero.
- */
-guint
-gsk_ngl_driver_release_render_target (GskNglDriver       *self,
-                                      GskNglRenderTarget *render_target,
-                                      gboolean            release_texture)
-{
-  guint texture_id;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), 0);
-  g_return_val_if_fail (render_target != NULL, 0);
-
-  if (release_texture)
-    {
-      texture_id = 0;
-      g_ptr_array_add (self->render_targets, render_target);
-    }
-  else
-    {
-      GskNglTexture *texture;
-
-      texture_id = render_target->texture_id;
-
-      texture = gsk_ngl_texture_new (render_target->texture_id,
-                                     render_target->width,
-                                     render_target->height,
-                                     render_target->format,
-                                     render_target->min_filter,
-                                     render_target->mag_filter,
-                                     self->current_frame_id);
-      g_hash_table_insert (self->textures,
-                           GUINT_TO_POINTER (texture_id),
-                           g_steal_pointer (&texture));
-
-      gsk_ngl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
-      g_slice_free (GskNglRenderTarget, render_target);
-
-    }
-
-  return texture_id;
-}
-
-/**
- * gsk_ngl_driver_lookup_shader:
- * @self: a `GskNglDriver`
- * @shader: the shader to lookup or load
- * @error: a location for a `GError`
- *
- * Attepts to load @shader from the shader cache.
- *
- * If it has not been loaded, then it will compile the shader on demand.
- *
- * Returns: (nullable) (transfer none): a `GskGLShader`
- */
-GskNglProgram *
-gsk_ngl_driver_lookup_shader (GskNglDriver  *self,
-                              GskGLShader   *shader,
-                              GError       **error)
-{
-  GskNglProgram *program;
-
-  g_return_val_if_fail (self != NULL, NULL);
-  g_return_val_if_fail (shader != NULL, NULL);
-
-  program = g_hash_table_lookup (self->shader_cache, shader);
-
-  if (program == NULL)
-    {
-      const GskGLUniform *uniforms;
-      GskNglCompiler *compiler;
-      GBytes *suffix;
-      int n_required_textures;
-      int n_uniforms;
-
-      uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
-      if (n_uniforms > GSK_NGL_PROGRAM_MAX_CUSTOM_ARGS)
-        {
-          g_set_error (error,
-                       GDK_GL_ERROR,
-                       GDK_GL_ERROR_UNSUPPORTED_FORMAT,
-                       "Tried to use %d uniforms, while only %d is supported",
-                       n_uniforms,
-                       GSK_NGL_PROGRAM_MAX_CUSTOM_ARGS);
-          return NULL;
-        }
-
-      n_required_textures = gsk_gl_shader_get_n_textures (shader);
-      if (n_required_textures > GSK_NGL_PROGRAM_MAX_CUSTOM_TEXTURES)
-        {
-          g_set_error (error,
-                       GDK_GL_ERROR,
-                       GDK_GL_ERROR_UNSUPPORTED_FORMAT,
-                       "Tried to use %d textures, while only %d is supported",
-                       n_required_textures,
-                       GSK_NGL_PROGRAM_MAX_CUSTOM_TEXTURES);
-          return NULL;
-        }
-
-      compiler = gsk_ngl_compiler_new (self, FALSE);
-      suffix = gsk_gl_shader_get_source (shader);
-
-      gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                                   GSK_NGL_COMPILER_ALL,
-                                                   "/org/gtk/libgsk/ngl/preamble.glsl");
-      gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                                   GSK_NGL_COMPILER_VERTEX,
-                                                   "/org/gtk/libgsk/ngl/preamble.vs.glsl");
-      gsk_ngl_compiler_set_preamble_from_resource (compiler,
-                                                   GSK_NGL_COMPILER_FRAGMENT,
-                                                   "/org/gtk/libgsk/ngl/preamble.fs.glsl");
-      gsk_ngl_compiler_set_source_from_resource (compiler,
-                                                 GSK_NGL_COMPILER_ALL,
-                                                 "/org/gtk/libgsk/ngl/custom.glsl");
-      gsk_ngl_compiler_set_suffix (compiler, GSK_NGL_COMPILER_FRAGMENT, suffix);
-
-      /* Setup attributes that are provided via VBO */
-      gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0);
-      gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1);
-      gsk_ngl_compiler_bind_attribute (compiler, "aColor", 2);
-      gsk_ngl_compiler_bind_attribute (compiler, "aColor2", 3);
-
-      if ((program = gsk_ngl_compiler_compile (compiler, NULL, "", error)))
-        {
-          gboolean have_alpha;
-
-          gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);
-          gsk_ngl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);
-          gsk_ngl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);
-          gsk_ngl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);
-          gsk_ngl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);
-          have_alpha = gsk_ngl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);
-
-          gsk_ngl_program_add_uniform (program, "u_size", UNIFORM_CUSTOM_SIZE);
-          gsk_ngl_program_add_uniform (program, "u_texture1", UNIFORM_CUSTOM_TEXTURE1);
-          gsk_ngl_program_add_uniform (program, "u_texture2", UNIFORM_CUSTOM_TEXTURE2);
-          gsk_ngl_program_add_uniform (program, "u_texture3", UNIFORM_CUSTOM_TEXTURE3);
-          gsk_ngl_program_add_uniform (program, "u_texture4", UNIFORM_CUSTOM_TEXTURE4);
-
-          /* Custom arguments (max is 8) */
-          for (guint i = 0; i < n_uniforms; i++)
-            gsk_ngl_program_add_uniform (program, uniforms[i].name, UNIFORM_CUSTOM_ARG0+i);
-
-          gsk_ngl_program_uniforms_added (program, TRUE);
-
-          if (have_alpha)
-            gsk_ngl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f);
-
-          g_hash_table_insert (self->shader_cache, shader, program);
-          g_object_weak_ref (G_OBJECT (shader),
-                             gsk_ngl_driver_shader_weak_cb,
-                             self);
-        }
-
-      g_object_unref (compiler);
-    }
-
-  return program;
-}
-
-#ifdef G_ENABLE_DEBUG
-static void
-write_atlas_to_png (GskNglDriver       *driver,
-                    GskNglTextureAtlas *atlas,
-                    const char         *filename)
-{
-  GdkTexture *texture;
-
-  texture = gdk_gl_texture_new (gsk_ngl_driver_get_context (driver),
-                                atlas->texture_id,
-                                atlas->width, atlas->height,
-                                NULL, NULL);
-  gdk_texture_save_to_png (texture, filename);
-  g_object_unref (texture);
-}
-
-void
-gsk_ngl_driver_save_atlases_to_png (GskNglDriver *self,
-                                    const char   *directory)
-{
-  g_return_if_fail (GSK_IS_NGL_DRIVER (self));
-
-  if (directory == NULL)
-    directory = ".";
-
-  for (guint i = 0; i < self->atlases->len; i++)
-    {
-      GskNglTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-      char *filename = g_strdup_printf ("%s%sframe-%d-atlas-%d.png",
-                                        directory,
-                                        G_DIR_SEPARATOR_S,
-                                        (int)self->current_frame_id,
-                                        atlas->texture_id);
-      write_atlas_to_png (self, atlas, filename);
-      g_free (filename);
-    }
-}
-#endif
-
-GskNglCommandQueue *
-gsk_ngl_driver_create_command_queue (GskNglDriver *self,
-                                     GdkGLContext *context)
-{
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
-
-  return gsk_ngl_command_queue_new (context, self->shared_command_queue->uniforms);
-}
-
-void
-gsk_ngl_driver_add_texture_slices (GskNglDriver        *self,
-                                   GdkTexture          *texture,
-                                   GskNglTextureSlice **out_slices,
-                                   guint               *out_n_slices)
-{
-  int max_texture_size;
-  GskNglTextureSlice *slices;
-  GskNglTexture *t;
-  guint n_slices;
-  guint cols;
-  guint rows;
-  int tex_width;
-  int tex_height;
-  int x = 0, y = 0;
-
-  g_assert (GSK_IS_NGL_DRIVER (self));
-  g_assert (GDK_IS_TEXTURE (texture));
-  g_assert (out_slices != NULL);
-  g_assert (out_n_slices != NULL);
-
-  /* XXX: Too much? */
-  max_texture_size = self->command_queue->max_texture_size / 4;
-
-  tex_width = texture->width;
-  tex_height = texture->height;
-  cols = (texture->width / max_texture_size) + 1;
-  rows = (texture->height / max_texture_size) + 1;
-
-  if ((t = gdk_texture_get_render_data (texture, self)))
-    {
-      *out_slices = t->slices;
-      *out_n_slices = t->n_slices;
-      return;
-    }
-
-  n_slices = cols * rows;
-  slices = g_new0 (GskNglTextureSlice, n_slices);
-
-  for (guint col = 0; col < cols; col ++)
-    {
-      int slice_width = MIN (max_texture_size, texture->width - x);
-
-      for (guint row = 0; row < rows; row ++)
-        {
-          int slice_height = MIN (max_texture_size, texture->height - y);
-          int slice_index = (col * rows) + row;
-          guint texture_id;
-
-          texture_id = gsk_ngl_command_queue_upload_texture (self->command_queue,
-                                                             texture,
-                                                             x, y,
-                                                             slice_width, slice_height,
-                                                             GL_NEAREST, GL_NEAREST);
-
-          slices[slice_index].rect.x = x;
-          slices[slice_index].rect.y = y;
-          slices[slice_index].rect.width = slice_width;
-          slices[slice_index].rect.height = slice_height;
-          slices[slice_index].texture_id = texture_id;
-
-          y += slice_height;
-        }
-
-      y = 0;
-      x += slice_width;
-    }
-
-  /* Allocate one Texture for the entire thing. */
-  t = gsk_ngl_texture_new (0,
-                           tex_width, tex_height,
-                           GL_RGBA8,
-                           GL_NEAREST, GL_NEAREST,
-                           self->current_frame_id);
-
-  /* Use gsk_ngl_texture_free() as destroy notify here since we are
-   * not inserting this GskNglTexture into self->textures!
-   */
-  gdk_texture_set_render_data (texture, self, t,
-                               (GDestroyNotify)gsk_ngl_texture_free);
-
-  t->slices = *out_slices = slices;
-  t->n_slices = *out_n_slices = n_slices;
-}
-
-GskNglTexture *
-gsk_ngl_driver_mark_texture_permanent (GskNglDriver *self,
-                                       guint         texture_id)
-{
-  GskNglTexture *t;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-  g_return_val_if_fail (texture_id > 0, NULL);
-
-  if ((t = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
-    t->permanent = TRUE;
-
-  return t;
-}
-
-void
-gsk_ngl_driver_release_texture_by_id (GskNglDriver *self,
-                                      guint         texture_id)
-{
-  GskNglTexture *texture;
-
-  g_return_if_fail (GSK_IS_NGL_DRIVER (self));
-  g_return_if_fail (texture_id > 0);
-
-  remove_texture_key_for_id (self, texture_id);
-
-  if ((texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
-    gsk_ngl_driver_release_texture (self, texture);
-}
-
-typedef struct _GskNglTextureState
-{
-  GdkGLContext *context;
-  GLuint        texture_id;
-} GskNglTextureState;
-
-static void
-create_texture_from_texture_destroy (gpointer data)
-{
-  GskNglTextureState *state = data;
-
-  g_assert (state != NULL);
-  g_assert (GDK_IS_GL_CONTEXT (state->context));
-
-  gdk_gl_context_make_current (state->context);
-  glDeleteTextures (1, &state->texture_id);
-  g_clear_object (&state->context);
-  g_slice_free (GskNglTextureState, state);
-}
-
-GdkTexture *
-gsk_ngl_driver_create_gdk_texture (GskNglDriver *self,
-                                   guint         texture_id)
-{
-  GskNglTextureState *state;
-  GskNglTexture *texture;
-  int width, height;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
-  g_return_val_if_fail (self->command_queue != NULL, NULL);
-  g_return_val_if_fail (GDK_IS_GL_CONTEXT (self->command_queue->context), NULL);
-  g_return_val_if_fail (texture_id > 0, NULL);
-  g_return_val_if_fail (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)), NULL);
-
-  /* We must be tracking this texture_id already to use it */
-  if (!(texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id))))
-    g_return_val_if_reached (NULL);
-
-  state = g_slice_new0 (GskNglTextureState);
-  state->texture_id = texture_id;
-  state->context = g_object_ref (self->command_queue->context);
-
-  g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
-
-  width = texture->width;
-  height = texture->height;
-
-  texture->texture_id = 0;
-  gsk_ngl_texture_free (texture);
-
-  return gdk_gl_texture_new (self->command_queue->context,
-                             texture_id,
-                             width,
-                             height,
-                             create_texture_from_texture_destroy,
-                             state);
-}
diff --git a/gsk/ngl/gskngldriverprivate.h b/gsk/ngl/gskngldriverprivate.h
deleted file mode 100644 (file)
index 42b6263..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-/* gskngldriverprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_DRIVER_PRIVATE_H__
-#define __GSK_NGL_DRIVER_PRIVATE_H__
-
-#include <gdk/gdkgltextureprivate.h>
-
-#include "gskngltypesprivate.h"
-#include "gskngltextureprivate.h"
-
-G_BEGIN_DECLS
-
-enum {
-  UNIFORM_SHARED_ALPHA,
-  UNIFORM_SHARED_SOURCE,
-  UNIFORM_SHARED_CLIP_RECT,
-  UNIFORM_SHARED_VIEWPORT,
-  UNIFORM_SHARED_PROJECTION,
-  UNIFORM_SHARED_MODELVIEW,
-
-  UNIFORM_SHARED_LAST
-};
-
-enum {
-  UNIFORM_CUSTOM_SIZE = UNIFORM_SHARED_LAST,
-  UNIFORM_CUSTOM_TEXTURE1,
-  UNIFORM_CUSTOM_TEXTURE2,
-  UNIFORM_CUSTOM_TEXTURE3,
-  UNIFORM_CUSTOM_TEXTURE4,
-  UNIFORM_CUSTOM_ARG0,
-  UNIFORM_CUSTOM_ARG1,
-  UNIFORM_CUSTOM_ARG2,
-  UNIFORM_CUSTOM_ARG3,
-  UNIFORM_CUSTOM_ARG4,
-  UNIFORM_CUSTOM_ARG5,
-  UNIFORM_CUSTOM_ARG6,
-  UNIFORM_CUSTOM_ARG7,
-
-  UNIFORM_CUSTOM_LAST
-};
-
-typedef struct {
-  gconstpointer   pointer;
-  float           scale_x;
-  float           scale_y;
-  int             filter;
-  int             pointer_is_child;
-  graphene_rect_t parent_rect; /* Valid when pointer_is_child */
-} GskTextureKey;
-
-#define GSK_NGL_NO_UNIFORMS CONCAT_EXPANDED(UNIFORM_INVALID_,__COUNTER__)
-#define CONCAT_EXPANDED(a,b) CONCAT_EXPANDED2(a,b)
-#define CONCAT_EXPANDED2(a,b) a##b
-#define GSK_NGL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos,
-#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms };
-# include "gsknglprograms.defs"
-#undef GSK_NGL_DEFINE_PROGRAM
-#undef GSK_NGL_ADD_UNIFORM
-#undef GSK_NGL_NO_UNIFORMS
-#undef CONCAT_EXPANDED
-#undef CONCAT_EXPANDED2
-
-#define GSK_TYPE_NGL_DRIVER (gsk_ngl_driver_get_type())
-
-G_DECLARE_FINAL_TYPE (GskNglDriver, gsk_ngl_driver, GSK, NGL_DRIVER, GObject)
-
-struct _GskNglRenderTarget
-{
-  guint framebuffer_id;
-  guint texture_id;
-  int min_filter;
-  int mag_filter;
-  int format;
-  int width;
-  int height;
-};
-
-struct _GskNglDriver
-{
-  GObject parent_instance;
-
-  GskNglCommandQueue *shared_command_queue;
-  GskNglCommandQueue *command_queue;
-
-  GskNglGlyphLibrary *glyphs;
-  GskNglIconLibrary *icons;
-  GskNglShadowLibrary *shadows;
-
-  GArray *texture_pool;
-  GHashTable *textures;
-  GHashTable *key_to_texture_id;
-  GHashTable *texture_id_to_key;
-
-  GPtrArray *atlases;
-
-  GHashTable *shader_cache;
-
-  GArray *autorelease_framebuffers;
-  GPtrArray *render_targets;
-
-#define GSK_NGL_NO_UNIFORMS
-#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)
-#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) \
-  GskNglProgram *name ## _no_clip; \
-  GskNglProgram *name ## _rect_clip; \
-  GskNglProgram *name;
-# include "gsknglprograms.defs"
-#undef GSK_NGL_NO_UNIFORMS
-#undef GSK_NGL_ADD_UNIFORM
-#undef GSK_NGL_DEFINE_PROGRAM
-
-  gint64 current_frame_id;
-
-  /* Used to reduce number of comparisons */
-  guint stamps[UNIFORM_SHARED_LAST];
-
-  guint debug : 1;
-  guint in_frame : 1;
-};
-
-GskNglDriver       *gsk_ngl_driver_for_display            (GdkDisplay           *display,
-                                                           gboolean              debug_shaders,
-                                                           GError              **error);
-GskNglCommandQueue *gsk_ngl_driver_create_command_queue   (GskNglDriver         *self,
-                                                           GdkGLContext         *context);
-GdkGLContext       *gsk_ngl_driver_get_context            (GskNglDriver         *self);
-gboolean            gsk_ngl_driver_create_render_target   (GskNglDriver         *self,
-                                                           int                   width,
-                                                           int                   height,
-                                                           int                   format,
-                                                           int                   min_filter,
-                                                           int                   mag_filter,
-                                                           GskNglRenderTarget  **render_target);
-guint               gsk_ngl_driver_release_render_target  (GskNglDriver         *self,
-                                                           GskNglRenderTarget   *render_target,
-                                                           gboolean              release_texture);
-void                gsk_ngl_driver_begin_frame            (GskNglDriver         *self,
-                                                           GskNglCommandQueue   *command_queue);
-void                gsk_ngl_driver_end_frame              (GskNglDriver         *self);
-void                gsk_ngl_driver_after_frame            (GskNglDriver         *self);
-GdkTexture         *gsk_ngl_driver_create_gdk_texture     (GskNglDriver         *self,
-                                                           guint                 texture_id);
-void                gsk_ngl_driver_cache_texture          (GskNglDriver         *self,
-                                                           const GskTextureKey  *key,
-                                                           guint                 texture_id);
-guint               gsk_ngl_driver_load_texture           (GskNglDriver         *self,
-                                                           GdkTexture           *texture,
-                                                           int                   min_filter,
-                                                           int                   mag_filter);
-GskNglTexture      *gsk_ngl_driver_create_texture         (GskNglDriver         *self,
-                                                           float                 width,
-                                                           float                 height,
-                                                           int                   format,
-                                                           int                   min_filter,
-                                                           int                   mag_filter);
-void                gsk_ngl_driver_release_texture        (GskNglDriver         *self,
-                                                           GskNglTexture        *texture);
-void                gsk_ngl_driver_release_texture_by_id  (GskNglDriver         *self,
-                                                           guint                 texture_id);
-GskNglTexture      *gsk_ngl_driver_mark_texture_permanent (GskNglDriver         *self,
-                                                           guint                 texture_id);
-void                gsk_ngl_driver_add_texture_slices     (GskNglDriver         *self,
-                                                           GdkTexture           *texture,
-                                                           GskNglTextureSlice  **out_slices,
-                                                           guint                *out_n_slices);
-GskNglProgram      *gsk_ngl_driver_lookup_shader          (GskNglDriver         *self,
-                                                           GskGLShader          *shader,
-                                                           GError              **error);
-GskNglTextureAtlas *gsk_ngl_driver_create_atlas           (GskNglDriver         *self);
-
-#ifdef G_ENABLE_DEBUG
-void                gsk_ngl_driver_save_atlases_to_png    (GskNglDriver         *self,
-                                                           const char           *directory);
-#endif
-
-static inline GskNglTexture *
-gsk_ngl_driver_get_texture_by_id (GskNglDriver *self,
-                                  guint         texture_id)
-{
-  return g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id));
-}
-
-/**
- * gsk_ngl_driver_lookup_texture:
- * @self: a `GskNglDriver`
- * @key: the key for the texture
- *
- * Looks up a texture in the texture cache by @key.
- *
- * If the texture could not be found, then zero is returned.
- *
- * Returns: a positive integer if the texture was found; otherwise 0.
- */
-static inline guint
-gsk_ngl_driver_lookup_texture (GskNglDriver        *self,
-                               const GskTextureKey *key)
-{
-  gpointer id;
-
-  if (g_hash_table_lookup_extended (self->key_to_texture_id, key, NULL, &id))
-    {
-      GskNglTexture *texture = g_hash_table_lookup (self->textures, id);
-
-      if (texture != NULL)
-        texture->last_used_in_frame = self->current_frame_id;
-
-      return GPOINTER_TO_UINT (id);
-    }
-
-  return 0;
-}
-
-static inline void
-gsk_ngl_driver_slice_texture (GskNglDriver        *self,
-                              GdkTexture          *texture,
-                              GskNglTextureSlice **out_slices,
-                              guint               *out_n_slices)
-{
-  GskNglTexture *t;
-
-  if ((t = gdk_texture_get_render_data (texture, self)))
-    {
-      *out_slices = t->slices;
-      *out_n_slices = t->n_slices;
-      return;
-    }
-
-  gsk_ngl_driver_add_texture_slices (self, texture, out_slices, out_n_slices);
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglglyphlibrary.c b/gsk/ngl/gsknglglyphlibrary.c
deleted file mode 100644 (file)
index ede4f19..0000000
+++ /dev/null
@@ -1,326 +0,0 @@
-/* gsknglglyphlibrary.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdkmemoryformatprivate.h>
-#include <gdk/gdkprofilerprivate.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-#include "gsknglglyphlibraryprivate.h"
-
-#define MAX_GLYPH_SIZE 128
-
-G_DEFINE_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
-
-GskNglGlyphLibrary *
-gsk_ngl_glyph_library_new (GskNglDriver *driver)
-{
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-
-  return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
-                       "driver", driver,
-                       NULL);
-}
-
-static guint
-gsk_ngl_glyph_key_hash (gconstpointer data)
-{
-  const GskNglGlyphKey *key = data;
-
-  /* We do not store the hash within the key because GHashTable will already
-   * store the hash value for us and so this is called only a single time per
-   * cached item. This saves an extra 4 bytes per GskNglGlyphKey which means on
-   * 64-bit, we fit nicely within 2 pointers (the smallest allocation size
-   * for GSlice).
-   */
-
-  return GPOINTER_TO_UINT (key->font) ^
-         key->glyph ^
-         (key->xshift << 24) ^
-         (key->yshift << 26) ^
-         key->scale;
-}
-
-static gboolean
-gsk_ngl_glyph_key_equal (gconstpointer v1,
-                         gconstpointer v2)
-{
-  return memcmp (v1, v2, sizeof (GskNglGlyphKey)) == 0;
-}
-
-static void
-gsk_ngl_glyph_key_free (gpointer data)
-{
-  GskNglGlyphKey *key = data;
-
-  g_clear_object (&key->font);
-  g_slice_free (GskNglGlyphKey, key);
-}
-
-static void
-gsk_ngl_glyph_value_free (gpointer data)
-{
-  g_slice_free (GskNglGlyphValue, data);
-}
-
-static void
-gsk_ngl_glyph_library_begin_frame (GskNglTextureLibrary *library,
-                                   gint64                frame_id,
-                                   GPtrArray            *removed_atlases)
-{
-  GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)library;
-
-  memset (self->front, 0, sizeof self->front);
-}
-
-static void
-gsk_ngl_glyph_library_finalize (GObject *object)
-{
-  GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)object;
-
-  g_clear_pointer (&self->surface_data, g_free);
-
-  G_OBJECT_CLASS (gsk_ngl_glyph_library_parent_class)->finalize (object);
-}
-
-static void
-gsk_ngl_glyph_library_class_init (GskNglGlyphLibraryClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GskNglTextureLibraryClass *library_class = GSK_NGL_TEXTURE_LIBRARY_CLASS (klass);
-
-  object_class->finalize = gsk_ngl_glyph_library_finalize;
-
-  library_class->begin_frame = gsk_ngl_glyph_library_begin_frame;
-}
-
-static void
-gsk_ngl_glyph_library_init (GskNglGlyphLibrary *self)
-{
-  GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
-
-  tl->max_entry_size = MAX_GLYPH_SIZE;
-  gsk_ngl_texture_library_set_funcs (tl,
-                                     gsk_ngl_glyph_key_hash,
-                                     gsk_ngl_glyph_key_equal,
-                                     gsk_ngl_glyph_key_free,
-                                     gsk_ngl_glyph_value_free);
-}
-
-static cairo_surface_t *
-gsk_ngl_glyph_library_create_surface (GskNglGlyphLibrary *self,
-                                      int                 stride,
-                                      int                 width,
-                                      int                 height,
-                                      int                 uwidth,
-                                      int                 uheight)
-{
-  cairo_surface_t *surface;
-  gsize n_bytes;
-
-  g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
-  g_assert (width > 0);
-  g_assert (height > 0);
-
-  n_bytes = stride * height;
-
-  if G_LIKELY (n_bytes > self->surface_data_len)
-    {
-      self->surface_data = g_realloc (self->surface_data, n_bytes);
-      self->surface_data_len = n_bytes;
-    }
-
-  memset (self->surface_data, 0, n_bytes);
-  surface = cairo_image_surface_create_for_data (self->surface_data,
-                                                 CAIRO_FORMAT_ARGB32,
-                                                 width, height, stride);
-  cairo_surface_set_device_scale (surface, width / (double)uwidth, height / (double)uheight);
-
-  return surface;
-}
-
-static void
-render_glyph (cairo_surface_t           *surface,
-              const cairo_scaled_font_t *scaled_font,
-              const GskNglGlyphKey      *key,
-              const GskNglGlyphValue    *value)
-{
-  cairo_t *cr;
-  cairo_glyph_t glyph;
-
-  g_assert (surface != NULL);
-  g_assert (scaled_font != NULL);
-
-  cr = cairo_create (surface);
-  cairo_set_scaled_font (cr, scaled_font);
-  cairo_set_source_rgba (cr, 1, 1, 1, 1);
-
-  glyph.index = key->glyph;
-  glyph.x = 0.25 * key->xshift - value->ink_rect.x;
-  glyph.y = 0.25 * key->yshift - value->ink_rect.y;
-
-  cairo_show_glyphs (cr, &glyph, 1);
-  cairo_destroy (cr);
-
-  cairo_surface_flush (surface);
-}
-
-static void
-gsk_ngl_glyph_library_upload_glyph (GskNglGlyphLibrary     *self,
-                                    const GskNglGlyphKey   *key,
-                                    const GskNglGlyphValue *value,
-                                    int                     x,
-                                    int                     y,
-                                    int                     width,
-                                    int                     height,
-                                    int                     uwidth,
-                                    int                     uheight)
-{
-  GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
-  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
-  cairo_scaled_font_t *scaled_font;
-  cairo_surface_t *surface;
-  guchar *pixel_data;
-  guchar *free_data = NULL;
-  guint gl_format;
-  guint gl_type;
-  guint texture_id;
-  gsize stride;
-
-  g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
-  g_assert (key != NULL);
-  g_assert (value != NULL);
-
-  scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
-  if G_UNLIKELY (scaled_font == NULL ||
-                 cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS)
-    return;
-
-  stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
-
-  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
-                                          "Uploading glyph %d",
-                                          key->glyph);
-
-  surface = gsk_ngl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight);
-  render_glyph (surface, scaled_font, key, value);
-
-  texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value);
-
-  g_assert (texture_id > 0);
-
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4);
-  glBindTexture (GL_TEXTURE_2D, texture_id);
-
-  if G_UNLIKELY (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
-    {
-      pixel_data = free_data = g_malloc (width * height * 4);
-      gdk_memory_convert (pixel_data,
-                          width * 4,
-                          GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
-                          cairo_image_surface_get_data (surface),
-                          width * 4,
-                          GDK_MEMORY_DEFAULT,
-                          width, height);
-      gl_format = GL_RGBA;
-      gl_type = GL_UNSIGNED_BYTE;
-    }
-  else
-    {
-      pixel_data = cairo_image_surface_get_data (surface);
-      gl_format = GL_BGRA;
-      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
-    }
-
-  glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height,
-                   gl_format, gl_type, pixel_data);
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
-
-  cairo_surface_destroy (surface);
-  g_free (free_data);
-
-  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
-
-  tl->driver->command_queue->n_uploads++;
-
-  if (gdk_profiler_is_running ())
-    {
-      char message[64];
-      g_snprintf (message, sizeof message, "Size %dx%d", width, height);
-      gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Glyph", message);
-    }
-}
-
-gboolean
-gsk_ngl_glyph_library_add (GskNglGlyphLibrary      *self,
-                           GskNglGlyphKey          *key,
-                           const GskNglGlyphValue **out_value)
-{
-  GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
-  PangoRectangle ink_rect;
-  GskNglGlyphValue *value;
-  int width;
-  int height;
-  guint packed_x;
-  guint packed_y;
-
-  g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
-  g_assert (key != NULL);
-  g_assert (out_value != NULL);
-
-  pango_font_get_glyph_extents (key->font, key->glyph, &ink_rect, NULL);
-  pango_extents_to_pixels (&ink_rect, NULL);
-
-  ink_rect.x -= 1;
-  ink_rect.width += 2;
-  ink_rect.y -= 1;
-  ink_rect.height += 2;
-
-  width = (int) ceil (ink_rect.width * key->scale / 1024.0);
-  height = (int) ceil (ink_rect.height * key->scale / 1024.0);
-
-  value = gsk_ngl_texture_library_pack (tl,
-                                        key,
-                                        sizeof *value,
-                                        width,
-                                        height,
-                                        1,
-                                        &packed_x, &packed_y);
-
-  memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect);
-
-  if (key->scale > 0 && width > 0 && height > 0)
-    gsk_ngl_glyph_library_upload_glyph (self,
-                                        key,
-                                        value,
-                                        packed_x + 1,
-                                        packed_y + 1,
-                                        width,
-                                        height,
-                                        ink_rect.width,
-                                        ink_rect.height);
-
-  *out_value = value;
-
-  return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
-}
diff --git a/gsk/ngl/gsknglglyphlibraryprivate.h b/gsk/ngl/gsknglglyphlibraryprivate.h
deleted file mode 100644 (file)
index a9f099c..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/* gsknglglyphlibraryprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
-#define __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
-
-#include <pango/pango.h>
-
-#include "gskngltexturelibraryprivate.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_ngl_glyph_library_get_type())
-
-typedef struct _GskNglGlyphKey
-{
-  PangoFont *font;
-  PangoGlyph glyph;
-  guint xshift : 2;
-  guint yshift : 2;
-  guint scale  : 28; /* times 1024 */
-} GskNglGlyphKey;
-
-typedef struct _GskNglGlyphValue
-{
-  GskNglTextureAtlasEntry entry;
-  PangoRectangle ink_rect;
-} GskNglGlyphValue;
-
-#if GLIB_SIZEOF_VOID_P == 8
-G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 16);
-#elif GLIB_SIZEOF_VOID_P == 4
-G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 12);
-#endif
-
-G_DECLARE_FINAL_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK, NGL_GLYPH_LIBRARY, GskNglTextureLibrary)
-
-struct _GskNglGlyphLibrary
-{
-  GskNglTextureLibrary parent_instance;
-  guint8 *surface_data;
-  gsize surface_data_len;
-  struct {
-    GskNglGlyphKey key;
-    const GskNglGlyphValue *value;
-  } front[256];
-};
-
-GskNglGlyphLibrary *gsk_ngl_glyph_library_new (GskNglDriver            *driver);
-gboolean            gsk_ngl_glyph_library_add (GskNglGlyphLibrary      *self,
-                                               GskNglGlyphKey          *key,
-                                               const GskNglGlyphValue **out_value);
-
-static inline guint
-gsk_ngl_glyph_library_lookup_or_add (GskNglGlyphLibrary      *self,
-                                     const GskNglGlyphKey    *key,
-                                     const GskNglGlyphValue **out_value)
-{
-  GskNglTextureAtlasEntry *entry;
-  guint front_index = ((key->glyph << 2) | key->xshift) & 0xFF;
-
-  if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
-    {
-      *out_value = self->front[front_index].value;
-    }
-  else if (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
-    {
-      *out_value = (GskNglGlyphValue *)entry;
-      self->front[front_index].key = *key;
-      self->front[front_index].value = *out_value;
-    }
-  else
-    {
-      GskNglGlyphKey *k = g_slice_copy (sizeof *key, key);
-      g_object_ref (k->font);
-      gsk_ngl_glyph_library_add (self, k, out_value);
-      self->front[front_index].key = *key;
-      self->front[front_index].value = *out_value;
-    }
-
-  return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value);
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngliconlibrary.c b/gsk/ngl/gskngliconlibrary.c
deleted file mode 100644 (file)
index cfc662a..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-/* gskngliconlibrary.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdkmemoryformatprivate.h>
-#include <gdk/gdkprofilerprivate.h>
-#include <gdk/gdktextureprivate.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-#include "gskngliconlibraryprivate.h"
-
-struct _GskNglIconLibrary
-{
-  GskNglTextureLibrary parent_instance;
-};
-
-G_DEFINE_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
-
-GskNglIconLibrary *
-gsk_ngl_icon_library_new (GskNglDriver *driver)
-{
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-
-  return g_object_new (GSK_TYPE_GL_ICON_LIBRARY,
-                       "driver", driver,
-                       NULL);
-}
-
-static void
-gsk_ngl_icon_data_free (gpointer data)
-{
-  GskNglIconData *icon_data = data;
-
-  g_clear_object (&icon_data->source_texture);
-  g_slice_free (GskNglIconData, icon_data);
-}
-
-static void
-gsk_ngl_icon_library_class_init (GskNglIconLibraryClass *klass)
-{
-}
-
-static void
-gsk_ngl_icon_library_init (GskNglIconLibrary *self)
-{
-  GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
-
-  tl->max_entry_size = 128;
-  gsk_ngl_texture_library_set_funcs (tl,
-                                     NULL, NULL, NULL,
-                                     gsk_ngl_icon_data_free);
-}
-
-void
-gsk_ngl_icon_library_add (GskNglIconLibrary     *self,
-                          GdkTexture            *key,
-                          const GskNglIconData **out_value)
-{
-  GskNglTextureLibrary *tl = (GskNglTextureLibrary *)self;
-  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
-  cairo_surface_t *surface;
-  GskNglIconData *icon_data;
-  guint8 *pixel_data;
-  guint8 *surface_data;
-  guint8 *free_data = NULL;
-  guint gl_format;
-  guint gl_type;
-  guint packed_x;
-  guint packed_y;
-  int width;
-  int height;
-  guint texture_id;
-
-  g_assert (GSK_IS_NGL_ICON_LIBRARY (self));
-  g_assert (GDK_IS_TEXTURE (key));
-  g_assert (out_value != NULL);
-
-  width = key->width;
-  height = key->height;
-
-  icon_data = gsk_ngl_texture_library_pack (tl,
-                                            key,
-                                            sizeof (GskNglIconData),
-                                            width, height, 1,
-                                            &packed_x, &packed_y);
-  icon_data->source_texture = g_object_ref (key);
-
-  /* actually upload the texture */
-  surface = gdk_texture_download_surface (key);
-  surface_data = cairo_image_surface_get_data (surface);
-  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
-                                          "Uploading texture");
-
-  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
-    {
-      pixel_data = free_data = g_malloc (width * height * 4);
-      gdk_memory_convert (pixel_data, width * 4,
-                          GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
-                          surface_data, cairo_image_surface_get_stride (surface),
-                          GDK_MEMORY_DEFAULT, width, height);
-      gl_format = GL_RGBA;
-      gl_type = GL_UNSIGNED_BYTE;
-    }
-  else
-    {
-      pixel_data = surface_data;
-      gl_format = GL_BGRA;
-      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
-    }
-
-  texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
-
-  glBindTexture (GL_TEXTURE_2D, texture_id);
-
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + 1, packed_y + 1,
-                   width, height,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding top */
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + 1, packed_y,
-                   width, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding left */
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x, packed_y + 1,
-                   1, height,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding top left */
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x, packed_y,
-                   1, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-
-  /* Padding right */
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
-  glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + width + 1, packed_y + 1,
-                   1, height,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding top right */
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + width + 1, packed_y,
-                   1, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding bottom */
-  glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
-  glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1);
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + 1, packed_y + 1 + height,
-                   width, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding bottom left */
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x, packed_y + 1 + height,
-                   1, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Padding bottom right */
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, width);
-  glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1);
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   packed_x + 1 + width, packed_y + 1 + height,
-                   1, 1,
-                   gl_format, gl_type,
-                   pixel_data);
-  /* Reset this */
-  glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
-  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
-  glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);
-
-  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
-
-  *out_value = icon_data;
-
-  cairo_surface_destroy (surface);
-  g_free (free_data);
-
-  tl->driver->command_queue->n_uploads++;
-
-  if (gdk_profiler_is_running ())
-    {
-      char message[64];
-      g_snprintf (message, sizeof message, "Size %dx%d", width, height);
-      gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload Icon", message);
-    }
-}
diff --git a/gsk/ngl/gskngliconlibraryprivate.h b/gsk/ngl/gskngliconlibraryprivate.h
deleted file mode 100644 (file)
index 5fd1cb2..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/* gskngliconlibraryprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
-#define __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
-
-#include <pango/pango.h>
-
-#include "gskngltexturelibraryprivate.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_ICON_LIBRARY (gsk_ngl_icon_library_get_type())
-
-typedef struct _GskNglIconData
-{
-  GskNglTextureAtlasEntry entry;
-  GdkTexture *source_texture;
-} GskNglIconData;
-
-G_DECLARE_FINAL_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK, NGL_ICON_LIBRARY, GskNglTextureLibrary)
-
-GskNglIconLibrary *gsk_ngl_icon_library_new (GskNglDriver          *driver);
-void               gsk_ngl_icon_library_add (GskNglIconLibrary     *self,
-                                             GdkTexture            *key,
-                                             const GskNglIconData **out_value);
-
-static inline void
-gsk_ngl_icon_library_lookup_or_add (GskNglIconLibrary     *self,
-                                    GdkTexture            *key,
-                                    const GskNglIconData **out_value)
-{
-  GskNglTextureAtlasEntry *entry;
-
-  if G_LIKELY (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
-    *out_value = (GskNglIconData *)entry;
-  else
-    gsk_ngl_icon_library_add (self, key, out_value);
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_ICON_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglprogram.c b/gsk/ngl/gsknglprogram.c
deleted file mode 100644 (file)
index 7e00fa4..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-/* gsknglprogram.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include "gsknglcommandqueueprivate.h"
-#include "gsknglprogramprivate.h"
-#include "gskngluniformstateprivate.h"
-
-G_DEFINE_TYPE (GskNglProgram, gsk_ngl_program, G_TYPE_OBJECT)
-
-GskNglProgram *
-gsk_ngl_program_new (GskNglDriver *driver,
-                     const char   *name,
-                     int           program_id)
-{
-  GskNglProgram *self;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-  g_return_val_if_fail (program_id >= -1, NULL);
-
-  self = g_object_new (GSK_TYPE_GL_PROGRAM, NULL);
-  self->id = program_id;
-  self->name = g_strdup (name);
-  self->driver = g_object_ref (driver);
-  self->n_mappings = 0;
-
-  return self;
-}
-
-static void
-gsk_ngl_program_finalize (GObject *object)
-{
-  GskNglProgram *self = (GskNglProgram *)object;
-
-  if (self->id >= 0)
-    g_warning ("Leaking GLSL program %d (%s)",
-               self->id,
-               self->name ? self->name : "");
-
-  g_clear_pointer (&self->name, g_free);
-  g_clear_object (&self->driver);
-
-  G_OBJECT_CLASS (gsk_ngl_program_parent_class)->finalize (object);
-}
-
-static void
-gsk_ngl_program_class_init (GskNglProgramClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->finalize = gsk_ngl_program_finalize;
-}
-
-static void
-gsk_ngl_program_init (GskNglProgram *self)
-{
-  self->id = -1;
-
-  for (guint i = 0; i < G_N_ELEMENTS (self->mappings); i++)
-    self->mappings[i].location = -1;
-}
-
-/**
- * gsk_ngl_program_add_uniform:
- * @self: a `GskNglProgram`
- * @name: the name of the uniform such as "u_source"
- * @key: the identifier to use for the uniform
- *
- * This method will create a mapping between @key and the location
- * of the uniform on the GPU. This simplifies calling code to not
- * need to know where the uniform location is and only register it
- * when creating the program.
- *
- * You might use this with an enum of all your uniforms for the
- * program and then register each of them like:
- *
- * ```
- * gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SOURCE);
- * ```
- *
- * That allows you to set values for the program with something
- * like the following:
- *
- * ```
- * gsk_ngl_program_set_uniform1i (program, UNIFORM_SOURCE, 1);
- * ```
- *
- * Returns: %TRUE if the uniform was found; otherwise %FALSE
- */
-gboolean
-gsk_ngl_program_add_uniform (GskNglProgram *self,
-                             const char    *name,
-                             guint          key)
-{
-  GLint location;
-
-  g_return_val_if_fail (GSK_IS_NGL_PROGRAM (self), FALSE);
-  g_return_val_if_fail (name != NULL, FALSE);
-  g_return_val_if_fail (key < G_N_ELEMENTS (self->mappings), FALSE);
-
-  location = glGetUniformLocation (self->id, name);
-
-  /* Register the information even if unused */
-  self->mappings[key].name = g_intern_string (name);
-  self->mappings[key].location = location;
-  if (key >= self->n_mappings)
-    self->n_mappings = key + 1;
-
-#if 0
-  g_print ("program [%d] %s uniform %s [%u of %u] at location %d.\n",
-           self->id, self->name, name, key, self->n_mappings, location);
-#endif
-
-  return location > -1;
-}
-
-/**
- * gsk_ngl_program_delete:
- * @self: a `GskNglProgram`
- *
- * Deletes the GLSL program.
- */
-void
-gsk_ngl_program_delete (GskNglProgram *self)
-{
-  g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
-  g_return_if_fail (self->driver->command_queue != NULL);
-
-  gsk_ngl_command_queue_delete_program (self->driver->command_queue, self->id);
-  self->id = -1;
-}
-
-/**
- * gsk_ngl_program_uniforms_added:
- * @self: a `GskNglProgram`
- * @has_attachments: if any uniform is for a bind/texture attachment
- *
- * This function should be called after all of the uniforms ahve
- * been added with gsk_ngl_program_add_uniform().
- *
- * This function will setup the uniform state so that the program
- * has fast access to the data buffers without as many lookups at
- * runtime for comparison data.
- */
-void
-gsk_ngl_program_uniforms_added (GskNglProgram *self,
-                                gboolean       has_attachments)
-{
-  g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
-  g_return_if_fail (self->uniforms == NULL);
-
-  self->uniforms = self->driver->command_queue->uniforms;
-  self->program_info = gsk_ngl_uniform_state_get_program (self->uniforms, self->id, self->mappings, self->n_mappings);
-  self->program_info->has_attachments = has_attachments;
-}
diff --git a/gsk/ngl/gsknglprogramprivate.h b/gsk/ngl/gsknglprogramprivate.h
deleted file mode 100644 (file)
index ccf0b8a..0000000
+++ /dev/null
@@ -1,282 +0,0 @@
-/* gsknglprogramprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_PROGRAM_PRIVATE_H__
-#define __GSK_NGL_PROGRAM_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-
-#include "gsknglattachmentstateprivate.h"
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_PROGRAM (gsk_ngl_program_get_type())
-#define GSK_NGL_PROGRAM_MAX_CUSTOM_TEXTURES 4
-#define GSK_NGL_PROGRAM_MAX_CUSTOM_ARGS 8
-
-G_DECLARE_FINAL_TYPE (GskNglProgram, gsk_ngl_program, GSK, NGL_PROGRAM, GObject)
-
-struct _GskNglProgram
-{
-  GObject parent_instance;
-
-  int id;
-  char *name;
-  GskNglDriver *driver;
-
-  /* Cached pointer to avoid lots of pointer chasing/lookups */
-  GskNglUniformState *uniforms;
-  GskNglUniformProgram *program_info;
-
-  /* Static array for key->location transforms */
-  GskNglUniformMapping mappings[32];
-  guint n_mappings;
-};
-
-GskNglProgram *gsk_ngl_program_new            (GskNglDriver  *driver,
-                                               const char    *name,
-                                               int            program_id);
-gboolean       gsk_ngl_program_add_uniform    (GskNglProgram *self,
-                                               const char    *name,
-                                               guint          key);
-void           gsk_ngl_program_uniforms_added (GskNglProgram *self,
-                                               gboolean       has_attachments);
-void           gsk_ngl_program_delete         (GskNglProgram *self);
-
-static inline void
-gsk_ngl_program_set_uniform1fv (GskNglProgram *self,
-                                guint          key,
-                                guint          stamp,
-                                guint          count,
-                                const float   *values)
-{
-  gsk_ngl_uniform_state_set1fv (self->uniforms, self->program_info,
-                                key,
-                                stamp,
-                                count,
-                                values);
-}
-
-static inline void
-gsk_ngl_program_set_uniform2fv (GskNglProgram *self,
-                                guint          key,
-                                guint          stamp,
-                                guint          count,
-                                const float   *values)
-{
-  gsk_ngl_uniform_state_set2fv (self->uniforms, self->program_info,
-                                key,
-                                stamp,
-                                count,
-                                values);
-}
-
-static inline void
-gsk_ngl_program_set_uniform4fv (GskNglProgram *self,
-                                guint          key,
-                                guint          stamp,
-                                guint          count,
-                                const float   *values)
-{
-  gsk_ngl_uniform_state_set4fv (self->uniforms, self->program_info,
-                                key,
-                                stamp,
-                                count,
-                                values);
-}
-
-static inline void
-gsk_ngl_program_set_uniform_rounded_rect (GskNglProgram        *self,
-                                          guint                 key,
-                                          guint                 stamp,
-                                          const GskRoundedRect *rounded_rect)
-{
-  gsk_ngl_uniform_state_set_rounded_rect (self->uniforms, self->program_info,
-                                          key,
-                                          stamp,
-                                          rounded_rect);
-}
-
-static inline void
-gsk_ngl_program_set_uniform1i (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               int            value0)
-{
-  gsk_ngl_uniform_state_set1i (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0);
-}
-
-static inline void
-gsk_ngl_program_set_uniform2i (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               int            value0,
-                               int            value1)
-{
-  gsk_ngl_uniform_state_set2i (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1);
-}
-
-static inline void
-gsk_ngl_program_set_uniform3i (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               int            value0,
-                               int            value1,
-                               int            value2)
-{
-  gsk_ngl_uniform_state_set3i (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1, value2);
-}
-
-static inline void
-gsk_ngl_program_set_uniform4i (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               int            value0,
-                               int            value1,
-                               int            value2,
-                               int            value3)
-{
-  gsk_ngl_uniform_state_set4i (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1, value2, value3);
-}
-
-static inline void
-gsk_ngl_program_set_uniform1f (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               float          value0)
-{
-  gsk_ngl_uniform_state_set1f (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0);
-}
-
-static inline void
-gsk_ngl_program_set_uniform2f (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               float          value0,
-                               float          value1)
-{
-  gsk_ngl_uniform_state_set2f (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1);
-}
-
-static inline void
-gsk_ngl_program_set_uniform3f (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               float          value0,
-                               float          value1,
-                               float          value2)
-{
-  gsk_ngl_uniform_state_set3f (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1, value2);
-}
-
-static inline void
-gsk_ngl_program_set_uniform4f (GskNglProgram *self,
-                               guint          key,
-                               guint          stamp,
-                               float          value0,
-                               float          value1,
-                               float          value2,
-                               float          value3)
-{
-  gsk_ngl_uniform_state_set4f (self->uniforms,
-                               self->program_info,
-                               key,
-                               stamp,
-                               value0, value1, value2, value3);
-}
-
-static inline void
-gsk_ngl_program_set_uniform_color (GskNglProgram *self,
-                                   guint          key,
-                                   guint          stamp,
-                                   const GdkRGBA *color)
-{
-  gsk_ngl_uniform_state_set_color (self->uniforms,
-                                   self->program_info,
-                                   key,
-                                   stamp,
-                                   color);
-}
-
-static inline void
-gsk_ngl_program_set_uniform_texture (GskNglProgram *self,
-                                     guint          key,
-                                     guint          stamp,
-                                     GLenum         texture_target,
-                                     GLenum         texture_slot,
-                                     guint          texture_id)
-{
-  gsk_ngl_attachment_state_bind_texture (self->driver->command_queue->attachments,
-                                         texture_target,
-                                         texture_slot,
-                                         texture_id);
-  gsk_ngl_uniform_state_set_texture (self->uniforms,
-                                     self->program_info,
-                                     key,
-                                     stamp,
-                                     texture_slot);
-}
-
-static inline void
-gsk_ngl_program_set_uniform_matrix (GskNglProgram           *self,
-                                    guint                    key,
-                                    guint                    stamp,
-                                    const graphene_matrix_t *matrix)
-{
-  gsk_ngl_uniform_state_set_matrix (self->uniforms,
-                                    self->program_info,
-                                    key,
-                                    stamp,
-                                    matrix);
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_PROGRAM_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglprograms.defs b/gsk/ngl/gsknglprograms.defs
deleted file mode 100644 (file)
index f4f4a0d..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-GSK_NGL_DEFINE_PROGRAM (blend,
-                        "/org/gtk/libgsk/ngl/blend.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2)
-                        GSK_NGL_ADD_UNIFORM (2, BLEND_MODE, u_mode))
-
-GSK_NGL_DEFINE_PROGRAM (blit,
-                        "/org/gtk/libgsk/ngl/blit.glsl",
-                        GSK_NGL_NO_UNIFORMS)
-
-GSK_NGL_DEFINE_PROGRAM (blur,
-                        "/org/gtk/libgsk/ngl/blur.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius)
-                        GSK_NGL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size)
-                        GSK_NGL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir))
-
-GSK_NGL_DEFINE_PROGRAM (border,
-                        "/org/gtk/libgsk/ngl/border.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, BORDER_WIDTHS, u_widths)
-                        GSK_NGL_ADD_UNIFORM (2, BORDER_OUTLINE_RECT, u_outline_rect))
-
-GSK_NGL_DEFINE_PROGRAM (color,
-                        "/org/gtk/libgsk/ngl/color.glsl",
-                        GSK_NGL_NO_UNIFORMS)
-
-GSK_NGL_DEFINE_PROGRAM (coloring,
-                        "/org/gtk/libgsk/ngl/coloring.glsl",
-                        GSK_NGL_NO_UNIFORMS)
-
-GSK_NGL_DEFINE_PROGRAM (color_matrix,
-                        "/org/gtk/libgsk/ngl/color_matrix.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix)
-                        GSK_NGL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset))
-
-GSK_NGL_DEFINE_PROGRAM (conic_gradient,
-                        "/org/gtk/libgsk/ngl/conic_gradient.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops)
-                        GSK_NGL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
-                        GSK_NGL_ADD_UNIFORM (3, CONIC_GRADIENT_GEOMETRY, u_geometry))
-
-GSK_NGL_DEFINE_PROGRAM (cross_fade,
-                        "/org/gtk/libgsk/ngl/cross_fade.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress)
-                        GSK_NGL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2))
-
-GSK_NGL_DEFINE_PROGRAM (filled_border,
-                        "/org/gtk/libgsk/ngl/filled_border.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, FILLED_BORDER_WIDTHS, u_widths)
-                        GSK_NGL_ADD_UNIFORM (2, FILLED_BORDER_OUTLINE_RECT, u_outline_rect))
-
-GSK_NGL_DEFINE_PROGRAM (inset_shadow,
-                        "/org/gtk/libgsk/ngl/inset_shadow.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, INSET_SHADOW_SPREAD, u_spread)
-                        GSK_NGL_ADD_UNIFORM (2, INSET_SHADOW_OFFSET, u_offset)
-                        GSK_NGL_ADD_UNIFORM (3, INSET_SHADOW_OUTLINE_RECT, u_outline_rect))
-
-GSK_NGL_DEFINE_PROGRAM (linear_gradient,
-                        "/org/gtk/libgsk/ngl/linear_gradient.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops)
-                        GSK_NGL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
-                        GSK_NGL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points)
-                        GSK_NGL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat))
-
-GSK_NGL_DEFINE_PROGRAM (outset_shadow,
-                        "/org/gtk/libgsk/ngl/outset_shadow.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
-
-GSK_NGL_DEFINE_PROGRAM (radial_gradient,
-                        "/org/gtk/libgsk/ngl/radial_gradient.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops)
-                        GSK_NGL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
-                        GSK_NGL_ADD_UNIFORM (3, RADIAL_GRADIENT_REPEAT, u_repeat)
-                        GSK_NGL_ADD_UNIFORM (4, RADIAL_GRADIENT_RANGE, u_range)
-                        GSK_NGL_ADD_UNIFORM (5, RADIAL_GRADIENT_GEOMETRY, u_geometry))
-
-GSK_NGL_DEFINE_PROGRAM (repeat,
-                        "/org/gtk/libgsk/ngl/repeat.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds)
-                        GSK_NGL_ADD_UNIFORM (2, REPEAT_TEXTURE_RECT, u_texture_rect))
-
-GSK_NGL_DEFINE_PROGRAM (unblurred_outset_shadow,
-                        "/org/gtk/libgsk/ngl/unblurred_outset_shadow.glsl",
-                        GSK_NGL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
-                        GSK_NGL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
-                        GSK_NGL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
diff --git a/gsk/ngl/gsknglrenderer.c b/gsk/ngl/gsknglrenderer.c
deleted file mode 100644 (file)
index eea48b6..0000000
+++ /dev/null
@@ -1,322 +0,0 @@
-/* gsknglrenderer.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkprofilerprivate.h>
-#include <gdk/gdkdisplayprivate.h>
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdksurfaceprivate.h>
-#include <gsk/gskdebugprivate.h>
-#include <gsk/gskrendererprivate.h>
-#include <gsk/gskrendernodeprivate.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-#include "gsknglprogramprivate.h"
-#include "gsknglrenderjobprivate.h"
-#include "gsknglrendererprivate.h"
-
-struct _GskNglRendererClass
-{
-  GskRendererClass parent_class;
-};
-
-struct _GskNglRenderer
-{
-  GskRenderer parent_instance;
-
-  /* This context is used to swap buffers when we are rendering directly
-   * to a GDK surface. It is also used to locate the shared driver for
-   * the display that we use to drive the command queue.
-   */
-  GdkGLContext *context;
-
-  /* Our command queue is private to this renderer and talks to the GL
-   * context for our target surface. This ensure that framebuffer 0 matches
-   * the surface we care about. Since the context is shared with other
-   * contexts from other renderers on the display, texture atlases,
-   * programs, and other objects are available to them all.
-   */
-  GskNglCommandQueue *command_queue;
-
-  /* The driver manages our program state and command queues. It also
-   * deals with caching textures, shaders, shadows, glyph, and icon
-   * caches through various helpers.
-   */
-  GskNglDriver *driver;
-};
-
-G_DEFINE_TYPE (GskNglRenderer, gsk_ngl_renderer, GSK_TYPE_RENDERER)
-
-/**
- * gsk_ngl_renderer_new:
- *
- * Creates a new `GskRenderer` using the new OpenGL renderer.
- *
- * Returns: a new NGL renderer
- *
- * Since: 4.2
- */
-GskRenderer *
-gsk_ngl_renderer_new (void)
-{
-  return g_object_new (GSK_TYPE_NGL_RENDERER, NULL);
-}
-
-static gboolean
-gsk_ngl_renderer_realize (GskRenderer  *renderer,
-                          GdkSurface   *surface,
-                          GError      **error)
-{
-  G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
-  GskNglRenderer *self = (GskNglRenderer *)renderer;
-  GdkGLContext *context = NULL;
-  GskNglDriver *driver = NULL;
-  gboolean ret = FALSE;
-  gboolean debug_shaders = FALSE;
-
-  g_assert (GSK_IS_NGL_RENDERER (self));
-  g_assert (GDK_IS_SURFACE (surface));
-
-  if (self->context != NULL)
-    return TRUE;
-
-  g_assert (self->driver == NULL);
-  g_assert (self->context == NULL);
-  g_assert (self->command_queue == NULL);
-
-  if (!(context = gdk_surface_create_gl_context (surface, error)) ||
-      !gdk_gl_context_realize (context, error))
-    goto failure;
-
-#ifdef G_ENABLE_DEBUG
-  if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
-    debug_shaders = TRUE;
-#endif
-
-  if (!(driver = gsk_ngl_driver_for_display (gdk_surface_get_display (surface), debug_shaders, error)))
-    goto failure;
-
-  self->command_queue = gsk_ngl_driver_create_command_queue (driver, context);
-  self->context = g_steal_pointer (&context);
-  self->driver = g_steal_pointer (&driver);
-
-  gsk_ngl_command_queue_set_profiler (self->command_queue,
-                                      gsk_renderer_get_profiler (renderer));
-
-  ret = TRUE;
-
-failure:
-  g_clear_object (&driver);
-  g_clear_object (&context);
-
-  gdk_profiler_end_mark (start_time, "realize GskNglRenderer", NULL);
-
-  return ret;
-}
-
-static void
-gsk_ngl_renderer_unrealize (GskRenderer *renderer)
-{
-  GskNglRenderer *self = (GskNglRenderer *)renderer;
-
-  g_assert (GSK_IS_NGL_RENDERER (renderer));
-
-  gdk_gl_context_make_current (self->context);
-
-  g_clear_object (&self->driver);
-  g_clear_object (&self->command_queue);
-  g_clear_object (&self->context);
-}
-
-static cairo_region_t *
-get_render_region (GdkSurface   *surface,
-                   GdkGLContext *context)
-{
-  const cairo_region_t *damage;
-  GdkRectangle whole_surface;
-  GdkRectangle extents;
-
-  g_assert (GDK_IS_SURFACE (surface));
-  g_assert (GDK_IS_GL_CONTEXT (context));
-
-  whole_surface.x = 0;
-  whole_surface.y = 0;
-  whole_surface.width = gdk_surface_get_width (surface);
-  whole_surface.height = gdk_surface_get_height (surface);
-
-  /* Damage does not have scale factor applied so we can compare it to
-   * @whole_surface which also doesn't have the scale factor applied.
-   */
-  damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (context));
-
-  if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
-    return NULL;
-
-  /* If the extents match the full-scene, do the same as above */
-  cairo_region_get_extents (damage, &extents);
-  if (gdk_rectangle_equal (&extents, &whole_surface))
-    return NULL;
-
-  /* Draw clipped to the bounding-box of the region. */
-  return cairo_region_create_rectangle (&extents);
-}
-
-static void
-gsk_ngl_renderer_render (GskRenderer          *renderer,
-                         GskRenderNode        *root,
-                         const cairo_region_t *update_area)
-{
-  GskNglRenderer *self = (GskNglRenderer *)renderer;
-  cairo_region_t *render_region;
-  graphene_rect_t viewport;
-  GskNglRenderJob *job;
-  GdkSurface *surface;
-  float scale_factor;
-
-  g_assert (GSK_IS_NGL_RENDERER (renderer));
-  g_assert (root != NULL);
-
-  surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context));
-  scale_factor = gdk_surface_get_scale_factor (surface);
-
-  viewport.origin.x = 0;
-  viewport.origin.y = 0;
-  viewport.size.width = gdk_surface_get_width (surface) * scale_factor;
-  viewport.size.height = gdk_surface_get_height (surface) * scale_factor;
-
-  gdk_gl_context_make_current (self->context);
-  gdk_draw_context_begin_frame_full (GDK_DRAW_CONTEXT (self->context),
-                                     gsk_render_node_prefers_high_depth (root),
-                                     update_area);
-
-  /* Must be called *AFTER* gdk_draw_context_begin_frame() */
-  render_region = get_render_region (surface, self->context);
-
-  gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
-  job = gsk_ngl_render_job_new (self->driver, &viewport, scale_factor, render_region, 0);
-#ifdef G_ENABLE_DEBUG
-  if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
-    gsk_ngl_render_job_set_debug_fallback (job, TRUE);
-#endif
-  gsk_ngl_render_job_render (job, root);
-  gsk_ngl_driver_end_frame (self->driver);
-  gsk_ngl_render_job_free (job);
-
-  gdk_gl_context_make_current (self->context);
-  gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->context));
-
-  gsk_ngl_driver_after_frame (self->driver);
-
-  cairo_region_destroy (render_region);
-}
-
-static GdkTexture *
-gsk_ngl_renderer_render_texture (GskRenderer           *renderer,
-                                 GskRenderNode         *root,
-                                 const graphene_rect_t *viewport)
-{
-  GskNglRenderer *self = (GskNglRenderer *)renderer;
-  GskNglRenderTarget *render_target;
-  GskNglRenderJob *job;
-  GdkTexture *texture = NULL;
-  guint texture_id;
-  int width;
-  int height;
-  int format;
-
-  g_assert (GSK_IS_NGL_RENDERER (renderer));
-  g_assert (root != NULL);
-
-  width = ceilf (viewport->size.width);
-  height = ceilf (viewport->size.height);
-
-  format = gsk_render_node_prefers_high_depth (root) ? GL_RGBA32F : GL_RGBA8;
-
-  if (gsk_ngl_driver_create_render_target (self->driver,
-                                           width, height,
-                                           format,
-                                           GL_NEAREST, GL_NEAREST,
-                                           &render_target))
-    {
-      gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
-      job = gsk_ngl_render_job_new (self->driver, viewport, 1, NULL, render_target->framebuffer_id);
-#ifdef G_ENABLE_DEBUG
-      if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
-        gsk_ngl_render_job_set_debug_fallback (job, TRUE);
-#endif
-      gsk_ngl_render_job_render_flipped (job, root);
-      texture_id = gsk_ngl_driver_release_render_target (self->driver, render_target, FALSE);
-      texture = gsk_ngl_driver_create_gdk_texture (self->driver, texture_id);
-      gsk_ngl_driver_end_frame (self->driver);
-      gsk_ngl_render_job_free (job);
-
-      gsk_ngl_driver_after_frame (self->driver);
-    }
-
-  return g_steal_pointer (&texture);
-}
-
-static void
-gsk_ngl_renderer_dispose (GObject *object)
-{
-#ifdef G_ENABLE_DEBUG
-  GskNglRenderer *self = (GskNglRenderer *)object;
-
-  g_assert (self->driver == NULL);
-#endif
-
-  G_OBJECT_CLASS (gsk_ngl_renderer_parent_class)->dispose (object);
-}
-
-static void
-gsk_ngl_renderer_class_init (GskNglRendererClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
-
-  object_class->dispose = gsk_ngl_renderer_dispose;
-
-  renderer_class->realize = gsk_ngl_renderer_realize;
-  renderer_class->unrealize = gsk_ngl_renderer_unrealize;
-  renderer_class->render = gsk_ngl_renderer_render;
-  renderer_class->render_texture = gsk_ngl_renderer_render_texture;
-}
-
-static void
-gsk_ngl_renderer_init (GskNglRenderer *self)
-{
-}
-
-gboolean
-gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer  *renderer,
-                                        GskGLShader     *shader,
-                                        GError         **error)
-{
-  GskNglProgram *program;
-
-  g_return_val_if_fail (GSK_IS_NGL_RENDERER (renderer), FALSE);
-  g_return_val_if_fail (shader != NULL, FALSE);
-
-  program = gsk_ngl_driver_lookup_shader (renderer->driver, shader, error);
-
-  return program != NULL;
-}
diff --git a/gsk/ngl/gsknglrenderer.h b/gsk/ngl/gsknglrenderer.h
deleted file mode 100644 (file)
index aa33f22..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/* gsknglrenderer.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_RENDERER_H__
-#define __GSK_NGL_RENDERER_H__
-
-#include <gsk/gskrenderer.h>
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_NGL_RENDERER (gsk_ngl_renderer_get_type())
-
-#define GSK_NGL_RENDERER(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_NGL_RENDERER, GskNglRenderer))
-#define GSK_IS_NGL_RENDERER(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_NGL_RENDERER))
-#define GSK_NGL_RENDERER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
-#define GSK_IS_NGL_RENDERER_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_NGL_RENDERER))
-#define GSK_NGL_RENDERER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
-
-typedef struct _GskNglRenderer      GskNglRenderer;
-typedef struct _GskNglRendererClass GskNglRendererClass;
-
-GDK_AVAILABLE_IN_4_2
-GType        gsk_ngl_renderer_get_type (void) G_GNUC_CONST;
-GDK_AVAILABLE_IN_4_2
-GskRenderer *gsk_ngl_renderer_new      (void);
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_RENDERER__ */
diff --git a/gsk/ngl/gsknglrendererprivate.h b/gsk/ngl/gsknglrendererprivate.h
deleted file mode 100644 (file)
index a0a861f..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* gsknglrendererprivate.h
- *
- * Copyright 2021 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_RENDERER_PRIVATE_H__
-#define __GSK_NGL_RENDERER_PRIVATE_H__
-
-#include "gsknglrenderer.h"
-
-G_BEGIN_DECLS
-
-gboolean gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer  *renderer,
-                                                 GskGLShader     *shader,
-                                                 GError         **error);
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_RENDERER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglrenderjob.c b/gsk/ngl/gsknglrenderjob.c
deleted file mode 100644 (file)
index e3436e1..0000000
+++ /dev/null
@@ -1,4163 +0,0 @@
-/* gsknglrenderjob.c
- *
- * Copyright 2017 Timm Bäder <mail@baedert.org>
- * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
- * Copyright 2018 Alexander Larsson <alexl@redhat.com>
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gdk/gdkprofilerprivate.h>
-#include <gdk/gdkrgbaprivate.h>
-#include <gsk/gskrendernodeprivate.h>
-#include <gsk/gskglshaderprivate.h>
-#include <gdk/gdktextureprivate.h>
-#include <gsk/gsktransformprivate.h>
-#include <gsk/gskroundedrectprivate.h>
-#include <math.h>
-#include <string.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-#include "gsknglglyphlibraryprivate.h"
-#include "gskngliconlibraryprivate.h"
-#include "gsknglprogramprivate.h"
-#include "gsknglrenderjobprivate.h"
-#include "gsknglshadowlibraryprivate.h"
-
-#include "ninesliceprivate.h"
-#include "fp16private.h"
-
-#define ORTHO_NEAR_PLANE   -10000
-#define ORTHO_FAR_PLANE     10000
-#define MAX_GRADIENT_STOPS  6
-#define SHADOW_EXTRA_SIZE   4
-
-/* Make sure gradient stops fits in packed array_count */
-G_STATIC_ASSERT ((MAX_GRADIENT_STOPS * 5) < (1 << GSK_NGL_UNIFORM_ARRAY_BITS));
-
-#define rounded_rect_top_left(r)                                                        \
-  (GRAPHENE_RECT_INIT(r->bounds.origin.x,                                               \
-                      r->bounds.origin.y,                                               \
-                      r->corner[0].width, r->corner[0].height))
-#define rounded_rect_top_right(r) \
-  (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[1].width,   \
-                      r->bounds.origin.y, \
-                      r->corner[1].width, r->corner[1].height))
-#define rounded_rect_bottom_right(r) \
-  (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[2].width,   \
-                      r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
-                      r->corner[2].width, r->corner[2].height))
-#define rounded_rect_bottom_left(r)                                                     \
-  (GRAPHENE_RECT_INIT(r->bounds.origin.x,                                               \
-                      r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
-                      r->corner[3].width, r->corner[3].height))
-#define rounded_rect_corner0(r)   rounded_rect_top_left(r)
-#define rounded_rect_corner1(r)   rounded_rect_top_right(r)
-#define rounded_rect_corner2(r)   rounded_rect_bottom_right(r)
-#define rounded_rect_corner3(r)   rounded_rect_bottom_left(r)
-#define rounded_rect_corner(r, i) (rounded_rect_corner##i(r))
-#define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff))
-#define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha)
-
-typedef struct _GskNglRenderClip
-{
-  GskRoundedRect rect;
-  guint          is_rectilinear : 1;
-  guint          is_fully_contained : 1;
-} GskNglRenderClip;
-
-typedef struct _GskNglRenderModelview
-{
-  GskTransform *transform;
-  float scale_x;
-  float scale_y;
-  float dx;
-  float dy;
-  float offset_x_before;
-  float offset_y_before;
-  graphene_matrix_t matrix;
-} GskNglRenderModelview;
-
-struct _GskNglRenderJob
-{
-  /* The context containing the framebuffer we are drawing to. Generally this
-   * is the context of the surface but may be a shared context if rendering to
-   * an offscreen texture such as gsk_ngl_renderer_render_texture().
-   */
-  GdkGLContext *context;
-
-  /* The driver to be used. This is shared among all the renderers on a given
-   * GdkDisplay and uses the shared GL context to send commands.
-   */
-  GskNglDriver *driver;
-
-  /* The command queue (which is just a faster pointer to the driver's
-   * command queue.
-   */
-  GskNglCommandQueue *command_queue;
-
-  /* The region that we are clipping. Normalized to a single rectangle region. */
-  cairo_region_t *region;
-
-  /* The framebuffer to draw to in the @context GL context. So 0 would be the
-   * default framebuffer of @context. This is important to note as many other
-   * operations could be done using objects shared from the command queues
-   * GL context.
-   */
-  guint framebuffer;
-
-  /* The viewport we are using. This state is updated as we process render
-   * nodes in the specific visitor callbacks.
-   */
-  graphene_rect_t viewport;
-
-  /* The current projection, updated as we process nodes */
-  graphene_matrix_t projection;
-
-  /* An array of GskNglRenderModelview updated as nodes are processed. The
-   * current modelview is the last element.
-   */
-  GArray *modelview;
-
-  /* An array of GskNglRenderClip updated as nodes are processed. The
-   * current clip is the last element.
-   */
-  GArray *clip;
-
-  /* Our current alpha state as we process nodes */
-  float alpha;
-
-  /* Offset (delta x,y) as we process nodes. Occasionally this is merged into
-   * a transform that is referenced from child transform nodes.
-   */
-  float offset_x;
-  float offset_y;
-
-  /* The scale we are processing, possibly updated by transforms */
-  float scale_x;
-  float scale_y;
-
-  /* Cached pointers */
-  const GskNglRenderClip *current_clip;
-  const GskNglRenderModelview *current_modelview;
-  GskNglProgram *current_program;
-
-  /* If we should be rendering red zones over fallback nodes */
-  guint debug_fallback : 1;
-
-  /* Format we want to use for intermediate textures, determined by
-   * looking at the format of the framebuffer we are rendering on.
-   */
-  int target_format;
-};
-
-typedef struct _GskNglRenderOffscreen
-{
-  /* The bounds to render */
-  const graphene_rect_t *bounds;
-
-  /* Return location for texture coordinates */
-  struct {
-    float x;
-    float y;
-    float x2;
-    float y2;
-  } area;
-
-  /* Return location for texture ID */
-  guint texture_id;
-
-  /* Whether to force creating a new texture, even if the
-   * input already is a texture
-   */
-  guint force_offscreen : 1;
-  guint reset_clip : 1;
-  guint do_not_cache : 1;
-  guint linear_filter : 1;
-
-  /* Return location for whether we created a texture */
-  guint was_offscreen : 1;
-} GskNglRenderOffscreen;
-
-static void     gsk_ngl_render_job_visit_node                (GskNglRenderJob       *job,
-                                                              const GskRenderNode  *node);
-static gboolean gsk_ngl_render_job_visit_node_with_offscreen (GskNglRenderJob       *job,
-                                                              const GskRenderNode  *node,
-                                                              GskNglRenderOffscreen *offscreen);
-
-static inline int
-get_target_format (GskNglRenderJob     *job,
-                   const GskRenderNode *node)
-{
-  if (gsk_render_node_prefers_high_depth (node))
-    return job->target_format;
-
-  return GL_RGBA8;
-}
-
-static inline void
-init_full_texture_region (GskNglRenderOffscreen *offscreen)
-{
-  offscreen->area.x = 0;
-  offscreen->area.y = 0;
-  offscreen->area.x2 = 1;
-  offscreen->area.y2 = 1;
-}
-
-static inline gboolean G_GNUC_PURE
-node_is_invisible (const GskRenderNode *node)
-{
-  return node->bounds.size.width == 0.0f ||
-         node->bounds.size.height == 0.0f;
-}
-
-static inline gboolean G_GNUC_PURE
-rounded_rect_equal (const GskRoundedRect *r1,
-                    const GskRoundedRect *r2)
-{
-  return memcmp (r1, r2, sizeof (GskRoundedRect)) == 0;
-}
-
-static inline void
-gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self)
-{
-  self->bounds.size.width  = MAX (self->corner[0].width + self->corner[1].width,
-                                  self->corner[3].width + self->corner[2].width);
-  self->bounds.size.height = MAX (self->corner[0].height + self->corner[3].height,
-                                  self->corner[1].height + self->corner[2].height);
-}
-
-static inline gboolean G_GNUC_PURE
-node_supports_transform (const GskRenderNode *node)
-{
-  /* Some nodes can't handle non-trivial transforms without being
-   * rendered to a texture (e.g. rotated clips, etc.). Some however work
-   * just fine, mostly because they already draw their child to a
-   * texture and just render the texture manipulated in some way, think
-   * opacity or color matrix.
-   */
-
-  switch ((int)gsk_render_node_get_node_type (node))
-    {
-      case GSK_COLOR_NODE:
-      case GSK_OPACITY_NODE:
-      case GSK_COLOR_MATRIX_NODE:
-      case GSK_TEXTURE_NODE:
-      case GSK_CROSS_FADE_NODE:
-      case GSK_LINEAR_GRADIENT_NODE:
-      case GSK_DEBUG_NODE:
-      case GSK_TEXT_NODE:
-        return TRUE;
-
-      case GSK_SHADOW_NODE:
-        return node_supports_transform (gsk_shadow_node_get_child (node));
-
-      case GSK_TRANSFORM_NODE:
-        return node_supports_transform (gsk_transform_node_get_child (node));
-
-      default:
-        return FALSE;
-    }
-}
-
-static inline gboolean G_GNUC_PURE
-color_matrix_modifies_alpha (const GskRenderNode *node)
-{
-  const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node);
-  const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node);
-  graphene_vec4_t row3;
-
-  if (graphene_vec4_get_w (offset) != 0.0f)
-    return TRUE;
-
-  graphene_matrix_get_row (matrix, 3, &row3);
-
-  return !graphene_vec4_equal (graphene_vec4_w_axis (), &row3);
-}
-
-static inline gboolean G_GNUC_PURE
-rect_contains_rect (const graphene_rect_t *r1,
-                    const graphene_rect_t *r2)
-{
-  return r2->origin.x >= r1->origin.x &&
-         (r2->origin.x + r2->size.width) <= (r1->origin.x + r1->size.width) &&
-         r2->origin.y >= r1->origin.y &&
-         (r2->origin.y + r2->size.height) <= (r1->origin.y + r1->size.height);
-}
-
-static inline gboolean
-rounded_inner_rect_contains_rect (const GskRoundedRect  *rounded,
-                                  const graphene_rect_t *rect)
-{
-  const graphene_rect_t *rounded_bounds = &rounded->bounds;
-  graphene_rect_t inner;
-  float offset_x;
-  float offset_y;
-
-  /* TODO: This is pretty conservative and we could go further,
-   *       more fine-grained checks to avoid offscreen drawing.
-   */
-
-  offset_x = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].width,
-                  rounded->corner[GSK_CORNER_BOTTOM_LEFT].width);
-  offset_y = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].height,
-                  rounded->corner[GSK_CORNER_TOP_RIGHT].height);
-
-  inner.origin.x = rounded_bounds->origin.x + offset_x;
-  inner.origin.y = rounded_bounds->origin.y + offset_y;
-  inner.size.width = rounded_bounds->size.width - offset_x -
-                     MAX (rounded->corner[GSK_CORNER_TOP_RIGHT].width,
-                          rounded->corner[GSK_CORNER_BOTTOM_RIGHT].width);
-  inner.size.height = rounded_bounds->size.height - offset_y -
-                      MAX (rounded->corner[GSK_CORNER_BOTTOM_LEFT].height,
-                           rounded->corner[GSK_CORNER_BOTTOM_RIGHT].height);
-
-  return rect_contains_rect (&inner, rect);
-}
-
-static inline gboolean G_GNUC_PURE
-rect_intersects (const graphene_rect_t *r1,
-                 const graphene_rect_t *r2)
-{
-  /* Assume both rects are already normalized, as they usually are */
-  if (r1->origin.x > (r2->origin.x + r2->size.width) ||
-      (r1->origin.x + r1->size.width) < r2->origin.x)
-    return FALSE;
-  else if (r1->origin.y > (r2->origin.y + r2->size.height) ||
-      (r1->origin.y + r1->size.height) < r2->origin.y)
-    return FALSE;
-  else
-    return TRUE;
-}
-
-static inline gboolean
-rounded_rect_has_corner (const GskRoundedRect *r,
-                         guint                 i)
-{
-  return r->corner[i].width > 0 && r->corner[i].height > 0;
-}
-
-/* Current clip is NOT rounded but new one is definitely! */
-static inline gboolean
-intersect_rounded_rectilinear (const graphene_rect_t *non_rounded,
-                               const GskRoundedRect  *rounded,
-                               GskRoundedRect        *result)
-{
-  gboolean corners[4];
-
-  /* Intersects with top left corner? */
-  corners[0] = rounded_rect_has_corner (rounded, 0) &&
-               rect_intersects (non_rounded,
-                                &rounded_rect_corner (rounded, 0));
-  if (corners[0] && !rect_contains_rect (non_rounded,
-                                         &rounded_rect_corner (rounded, 0)))
-    return FALSE;
-
-  /* top right ? */
-  corners[1] = rounded_rect_has_corner (rounded, 1) &&
-               rect_intersects (non_rounded,
-                                &rounded_rect_corner (rounded, 1));
-  if (corners[1] && !rect_contains_rect (non_rounded,
-                                         &rounded_rect_corner (rounded, 1)))
-    return FALSE;
-
-  /* bottom right ? */
-  corners[2] = rounded_rect_has_corner (rounded, 2) &&
-               rect_intersects (non_rounded,
-                                &rounded_rect_corner (rounded, 2));
-  if (corners[2] && !rect_contains_rect (non_rounded,
-                                         &rounded_rect_corner (rounded, 2)))
-    return FALSE;
-
-  /* bottom left ? */
-  corners[3] = rounded_rect_has_corner (rounded, 3) &&
-               rect_intersects (non_rounded,
-                                &rounded_rect_corner (rounded, 3));
-  if (corners[3] && !rect_contains_rect (non_rounded,
-                                         &rounded_rect_corner (rounded, 3)))
-    return FALSE;
-
-  /* We do intersect with at least one of the corners, but in such a way that the
-   * intersection between the two clips can still be represented by a single rounded
-   * rect in a trivial way. do that.
-   */
-  graphene_rect_intersection (non_rounded, &rounded->bounds, &result->bounds);
-
-  for (guint i = 0; i < 4; i++)
-    {
-      if (corners[i])
-        result->corner[i] = rounded->corner[i];
-      else
-        result->corner[i].width = result->corner[i].height = 0;
-    }
-
-  return TRUE;
-}
-
-static inline void
-init_projection_matrix (graphene_matrix_t     *projection,
-                        const graphene_rect_t *viewport)
-{
-  graphene_matrix_init_ortho (projection,
-                              viewport->origin.x,
-                              viewport->origin.x + viewport->size.width,
-                              viewport->origin.y,
-                              viewport->origin.y + viewport->size.height,
-                              ORTHO_NEAR_PLANE,
-                              ORTHO_FAR_PLANE);
-  graphene_matrix_scale (projection, 1, -1, 1);
-}
-
-static inline float
-gsk_ngl_render_job_set_alpha (GskNglRenderJob *job,
-                              float            alpha)
-{
-  if (job->alpha != alpha)
-    {
-      float ret = job->alpha;
-      job->alpha = alpha;
-      job->driver->stamps[UNIFORM_SHARED_ALPHA]++;
-      return ret;
-    }
-
-  return alpha;
-}
-
-static void
-extract_matrix_metadata (GskNglRenderModelview *modelview)
-{
-  gsk_transform_to_matrix (modelview->transform, &modelview->matrix);
-
-  switch (gsk_transform_get_category (modelview->transform))
-    {
-    case GSK_TRANSFORM_CATEGORY_IDENTITY:
-      modelview->scale_x = 1;
-      modelview->scale_y = 1;
-      modelview->dx = 0;
-      modelview->dy = 0;
-      break;
-
-    case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
-      modelview->scale_x = 1;
-      modelview->scale_y = 1;
-      gsk_transform_to_translate (modelview->transform,
-                                  &modelview->dx, &modelview->dy);
-      break;
-
-    case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
-      gsk_transform_to_affine (modelview->transform,
-                               &modelview->scale_x, &modelview->scale_y,
-                               &modelview->dx, &modelview->dy);
-      break;
-
-    case GSK_TRANSFORM_CATEGORY_2D:
-      {
-        float xx, xy, yx, yy, dx, dy;
-
-        gsk_transform_to_2d (modelview->transform,
-                             &xx, &xy, &yx, &yy, &dx, &dy);
-
-        modelview->scale_x = sqrtf (xx * xx + xy * xy);
-        modelview->scale_y = sqrtf (yx * yx + yy * yy);
-      }
-      break;
-
-    case GSK_TRANSFORM_CATEGORY_UNKNOWN:
-    case GSK_TRANSFORM_CATEGORY_ANY:
-    case GSK_TRANSFORM_CATEGORY_3D:
-      {
-        graphene_vec3_t col1;
-        graphene_vec3_t col2;
-
-        /* TODO: 90% sure this is incorrect. But we should never hit this code
-         * path anyway. */
-        graphene_vec3_init (&col1,
-                            graphene_matrix_get_value (&modelview->matrix, 0, 0),
-                            graphene_matrix_get_value (&modelview->matrix, 1, 0),
-                            graphene_matrix_get_value (&modelview->matrix, 2, 0));
-
-        graphene_vec3_init (&col2,
-                            graphene_matrix_get_value (&modelview->matrix, 0, 1),
-                            graphene_matrix_get_value (&modelview->matrix, 1, 1),
-                            graphene_matrix_get_value (&modelview->matrix, 2, 1));
-
-        modelview->scale_x = graphene_vec3_length (&col1);
-        modelview->scale_y = graphene_vec3_length (&col2);
-        modelview->dx = 0;
-        modelview->dy = 0;
-      }
-      break;
-
-    default:
-      break;
-    }
-}
-
-static void
-gsk_ngl_render_job_set_modelview (GskNglRenderJob *job,
-                                  GskTransform    *transform)
-{
-  GskNglRenderModelview *modelview;
-
-  g_assert (job != NULL);
-  g_assert (job->modelview != NULL);
-
-  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
-
-  g_array_set_size (job->modelview, job->modelview->len + 1);
-
-  modelview = &g_array_index (job->modelview,
-                              GskNglRenderModelview,
-                              job->modelview->len - 1);
-
-  modelview->transform = transform;
-
-  modelview->offset_x_before = job->offset_x;
-  modelview->offset_y_before = job->offset_y;
-
-  extract_matrix_metadata (modelview);
-
-  job->offset_x = 0;
-  job->offset_y = 0;
-  job->scale_x = modelview->scale_x;
-  job->scale_y = modelview->scale_y;
-
-  job->current_modelview = modelview;
-}
-
-static void
-gsk_ngl_render_job_push_modelview (GskNglRenderJob *job,
-                                   GskTransform    *transform)
-{
-  GskNglRenderModelview *modelview;
-
-  g_assert (job != NULL);
-  g_assert (job->modelview != NULL);
-  g_assert (transform != NULL);
-
-  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
-
-  g_array_set_size (job->modelview, job->modelview->len + 1);
-
-  modelview = &g_array_index (job->modelview,
-                              GskNglRenderModelview,
-                              job->modelview->len - 1);
-
-  if G_LIKELY (job->modelview->len > 1)
-    {
-      GskNglRenderModelview *last;
-      GskTransform *t = NULL;
-
-      last = &g_array_index (job->modelview,
-                             GskNglRenderModelview,
-                             job->modelview->len - 2);
-
-      /* Multiply given matrix with our previous modelview */
-      t = gsk_transform_translate (gsk_transform_ref (last->transform),
-                                   &(graphene_point_t) {
-                                     job->offset_x,
-                                     job->offset_y
-                                   });
-      t = gsk_transform_transform (t, transform);
-      modelview->transform = t;
-    }
-  else
-    {
-      modelview->transform = gsk_transform_ref (transform);
-    }
-
-  modelview->offset_x_before = job->offset_x;
-  modelview->offset_y_before = job->offset_y;
-
-  extract_matrix_metadata (modelview);
-
-  job->offset_x = 0;
-  job->offset_y = 0;
-  job->scale_x = modelview->scale_x;
-  job->scale_y = modelview->scale_y;
-
-  job->current_modelview = modelview;
-}
-
-static void
-gsk_ngl_render_job_pop_modelview (GskNglRenderJob *job)
-{
-  const GskNglRenderModelview *head;
-
-  g_assert (job != NULL);
-  g_assert (job->modelview);
-  g_assert (job->modelview->len > 0);
-
-  job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
-
-  head = job->current_modelview;
-
-  job->offset_x = head->offset_x_before;
-  job->offset_y = head->offset_y_before;
-
-  gsk_transform_unref (head->transform);
-
-  job->modelview->len--;
-
-  if (job->modelview->len >= 1)
-    {
-      head = &g_array_index (job->modelview, GskNglRenderModelview, job->modelview->len - 1);
-
-      job->scale_x = head->scale_x;
-      job->scale_y = head->scale_y;
-
-      job->current_modelview = head;
-    }
-  else
-    {
-      job->current_modelview = NULL;
-    }
-}
-
-static void
-gsk_ngl_render_job_push_clip (GskNglRenderJob      *job,
-                              const GskRoundedRect *rect)
-{
-  GskNglRenderClip *clip;
-
-  g_assert (job != NULL);
-  g_assert (job->clip != NULL);
-  g_assert (rect != NULL);
-
-  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
-
-  g_array_set_size (job->clip, job->clip->len + 1);
-
-  clip = &g_array_index (job->clip, GskNglRenderClip, job->clip->len - 1);
-  memcpy (&clip->rect, rect, sizeof *rect);
-  clip->is_rectilinear = gsk_rounded_rect_is_rectilinear (rect);
-  clip->is_fully_contained = FALSE;
-
-  job->current_clip = clip;
-}
-
-static void
-gsk_ngl_render_job_push_contained_clip (GskNglRenderJob *job)
-{
-  GskNglRenderClip *clip;
-  GskNglRenderClip *old_clip;
-
-  g_assert (job != NULL);
-  g_assert (job->clip != NULL);
-  g_assert (job->clip->len > 0);
-
-  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
-
-  old_clip = &g_array_index (job->clip, GskNglRenderClip, job->clip->len - 1);
-
-  g_array_set_size (job->clip, job->clip->len + 1);
-
-  clip = &g_array_index (job->clip, GskNglRenderClip, job->clip->len - 1);
-  memcpy (&clip->rect.bounds, &old_clip->rect.bounds, sizeof (graphene_rect_t));
-  memset (clip->rect.corner, 0, sizeof clip->rect.corner);
-  clip->is_rectilinear = TRUE;
-  clip->is_fully_contained = TRUE;
-
-  job->current_clip = clip;
-}
-
-static void
-gsk_ngl_render_job_pop_clip (GskNglRenderJob *job)
-{
-  g_assert (job != NULL);
-  g_assert (job->clip != NULL);
-  g_assert (job->clip->len > 0);
-
-  job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
-  job->current_clip--;
-  job->clip->len--;
-}
-
-static inline void
-gsk_ngl_render_job_offset (GskNglRenderJob *job,
-                           float            offset_x,
-                           float            offset_y)
-{
-  if (offset_x || offset_y)
-    {
-      job->offset_x += offset_x;
-      job->offset_y += offset_y;
-    }
-}
-
-static inline void
-gsk_ngl_render_job_set_projection (GskNglRenderJob         *job,
-                                   const graphene_matrix_t *projection)
-{
-  memcpy (&job->projection, projection, sizeof job->projection);
-  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
-}
-
-static inline void
-gsk_ngl_render_job_set_projection_from_rect (GskNglRenderJob       *job,
-                                             const graphene_rect_t *rect,
-                                             graphene_matrix_t     *prev_projection)
-{
-  if (prev_projection)
-    memcpy (prev_projection, &job->projection, sizeof *prev_projection);
-  init_projection_matrix (&job->projection, rect);
-  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
-}
-
-static inline void
-gsk_ngl_render_job_set_projection_for_size (GskNglRenderJob   *job,
-                                            float              width,
-                                            float              height,
-                                            graphene_matrix_t *prev_projection)
-{
-  if (prev_projection)
-    memcpy (prev_projection, &job->projection, sizeof *prev_projection);
-  graphene_matrix_init_ortho (&job->projection, 0, width, 0, height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE);
-  graphene_matrix_scale (&job->projection, 1, -1, 1);
-  job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
-}
-
-static inline void
-gsk_ngl_render_job_set_viewport (GskNglRenderJob       *job,
-                                 const graphene_rect_t *viewport,
-                                 graphene_rect_t       *prev_viewport)
-{
-  if (prev_viewport)
-    memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport);
-  memcpy (&job->viewport, viewport, sizeof job->viewport);
-  job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
-}
-
-static inline void
-gsk_ngl_render_job_set_viewport_for_size (GskNglRenderJob *job,
-                                          float            width,
-                                          float            height,
-                                          graphene_rect_t *prev_viewport)
-{
-  if (prev_viewport)
-    memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport);
-  job->viewport.origin.x = 0;
-  job->viewport.origin.y = 0;
-  job->viewport.size.width = width;
-  job->viewport.size.height = height;
-  job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++;
-}
-
-static inline void
-gsk_ngl_render_job_transform_bounds (GskNglRenderJob       *job,
-                                     const graphene_rect_t *rect,
-                                     graphene_rect_t       *out_rect)
-{
-  GskTransform *transform;
-  GskTransformCategory category;
-
-  g_assert (job != NULL);
-  g_assert (job->modelview->len > 0);
-  g_assert (rect != NULL);
-  g_assert (out_rect != NULL);
-
-  transform = job->current_modelview->transform;
-  category = gsk_transform_get_category (transform);
-
-  /* Our most common transform is 2d-affine, so inline it.
-   * Both identity and 2d-translate are virtually unseen here.
-   */
-  if G_LIKELY (category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE)
-    {
-      float scale_x = job->current_modelview->scale_x;
-      float scale_y = job->current_modelview->scale_y;
-      float dx = job->current_modelview->dx;
-      float dy = job->current_modelview->dy;
-
-      /* Init directly into out rect */
-      out_rect->origin.x = ((rect->origin.x + job->offset_x) * scale_x) + dx;
-      out_rect->origin.y = ((rect->origin.y + job->offset_y) * scale_y) + dy;
-      out_rect->size.width = rect->size.width * scale_x;
-      out_rect->size.height = rect->size.height * scale_y;
-
-      /* Normalize in place */
-      if (out_rect->size.width < 0.f)
-        {
-          float size = fabsf (out_rect->size.width);
-
-          out_rect->origin.x -= size;
-          out_rect->size.width = size;
-        }
-
-      if (out_rect->size.height < 0.f)
-        {
-          float size = fabsf (out_rect->size.height);
-
-          out_rect->origin.y -= size;
-          out_rect->size.height = size;
-        }
-    }
-  else
-    {
-      graphene_rect_t r;
-
-      r.origin.x = rect->origin.x + job->offset_x;
-      r.origin.y = rect->origin.y + job->offset_y;
-      r.size.width = rect->size.width;
-      r.size.height = rect->size.height;
-
-      gsk_transform_transform_bounds (transform, &r, out_rect);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_transform_rounded_rect (GskNglRenderJob      *job,
-                                           const GskRoundedRect *rect,
-                                           GskRoundedRect       *out_rect)
-{
-  out_rect->bounds.origin.x = job->offset_x + rect->bounds.origin.x;
-  out_rect->bounds.origin.y = job->offset_y + rect->bounds.origin.y;
-  out_rect->bounds.size.width = rect->bounds.size.width;
-  out_rect->bounds.size.height = rect->bounds.size.height;
-  memcpy (out_rect->corner, rect->corner, sizeof rect->corner);
-}
-
-static inline void
-rounded_rect_get_inner (const GskRoundedRect *rect,
-                        graphene_rect_t      *inner)
-{
-  float left = MAX (rect->corner[GSK_CORNER_TOP_LEFT].width, rect->corner[GSK_CORNER_BOTTOM_LEFT].width);
-  float right = MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->corner[GSK_CORNER_BOTTOM_RIGHT].width);
-  float top = MAX (rect->corner[GSK_CORNER_TOP_LEFT].height, rect->corner[GSK_CORNER_TOP_RIGHT].height);
-  float bottom = MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height, rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
-
-  inner->origin.x = rect->bounds.origin.x + left;
-  inner->size.width = rect->bounds.size.width - (left + right);
-
-  inner->origin.y = rect->bounds.origin.y + top;
-  inner->size.height = rect->bounds.size.height - (top + bottom);
-}
-
-static inline gboolean
-interval_contains (float p1, float w1,
-                   float p2, float w2)
-{
-  if (p2 < p1)
-    return FALSE;
-
-  if (p2 + w2 > p1 + w1)
-    return FALSE;
-
-  return TRUE;
-}
-
-static inline gboolean
-gsk_ngl_render_job_update_clip (GskNglRenderJob       *job,
-                                const graphene_rect_t *bounds,
-                                gboolean              *pushed_clip)
-{
-  graphene_rect_t transformed_bounds;
-  gboolean no_clip = FALSE;
-  gboolean rect_clip = FALSE;
-
-  *pushed_clip = FALSE;
-
-  if (job->current_clip->is_fully_contained)
-    {
-      /* Already fully contained - no further checks needed */
-      return TRUE;
-    }
-
-  gsk_ngl_render_job_transform_bounds (job, bounds, &transformed_bounds);
-
-  if (!rect_intersects (&job->current_clip->rect.bounds, &transformed_bounds))
-    {
-      /* Completely clipped away */
-      return FALSE;
-    }
-
-  if (job->current_clip->is_rectilinear)
-    {
-      if (rect_contains_rect (&job->current_clip->rect.bounds, &transformed_bounds))
-        no_clip = TRUE;
-      else
-        rect_clip = TRUE;
-    }
-  else if (gsk_rounded_rect_contains_rect (&job->current_clip->rect, &transformed_bounds))
-    {
-      no_clip = TRUE;
-    }
-  else
-    {
-      graphene_rect_t inner;
-
-      rounded_rect_get_inner (&job->current_clip->rect, &inner);
-
-      if (interval_contains (inner.origin.x, inner.size.width,
-                             transformed_bounds.origin.x, transformed_bounds.size.width) ||
-          interval_contains (inner.origin.y, inner.size.height,
-                             transformed_bounds.origin.y, transformed_bounds.size.height))
-        rect_clip = TRUE;
-    }
-
-  if (no_clip)
-    {
-      /* This node is completely contained inside the clip.
-       * Record this fact on the clip stack, so we don't do more work
-       * for child nodes.
-       */
-
-      gsk_ngl_render_job_push_contained_clip (job);
-
-      *pushed_clip = TRUE;
-    }
-  else if (rect_clip && !job->current_clip->is_rectilinear)
-    {
-      graphene_rect_t rect;
-
-      /* The clip gets simpler for this node */
-
-      graphene_rect_intersection (&job->current_clip->rect.bounds, &transformed_bounds, &rect);
-      gsk_ngl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (rect));
-
-      *pushed_clip = TRUE;
-    }
-
-  return TRUE;
-}
-
-static inline void
-rgba_to_half (const GdkRGBA *rgba,
-              guint16        h[4])
-{
-  float_to_half4 ((const float *)rgba, h);
-}
-
-/* fill_vertex_data */
-static void
-gsk_ngl_render_job_draw_coords (GskNglRenderJob *job,
-                                float            min_x,
-                                float            min_y,
-                                float            max_x,
-                                float            max_y,
-                                float            min_u,
-                                float            min_v,
-                                float            max_u,
-                                float            max_v,
-                                guint16          c[4])
-{
-  GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-  vertices[0] = (GskNglDrawVertex) { .position = { min_x, min_y }, .uv = { min_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
-  vertices[1] = (GskNglDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
-  vertices[2] = (GskNglDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
-  vertices[3] = (GskNglDrawVertex) { .position = { max_x, max_y }, .uv = { max_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
-  vertices[4] = (GskNglDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } };
-  vertices[5] = (GskNglDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } };
-}
-
-/* load_vertex_data_with_region */
-static inline void
-gsk_ngl_render_job_draw_offscreen_with_color (GskNglRenderJob             *job,
-                                              const graphene_rect_t       *bounds,
-                                              const GskNglRenderOffscreen *offscreen,
-                                              guint16                      color[4])
-{
-  float min_x = job->offset_x + bounds->origin.x;
-  float min_y = job->offset_y + bounds->origin.y;
-  float max_x = min_x + bounds->size.width;
-  float max_y = min_y + bounds->size.height;
-  float y1 = offscreen->was_offscreen ? offscreen->area.y2 : offscreen->area.y;
-  float y2 = offscreen->was_offscreen ? offscreen->area.y : offscreen->area.y2;
-
-  gsk_ngl_render_job_draw_coords (job,
-                                  min_x, min_y, max_x, max_y,
-                                  offscreen->area.x, y1, offscreen->area.x2, y2,
-                                  color);
-}
-
-static inline void
-gsk_ngl_render_job_draw_offscreen (GskNglRenderJob             *job,
-                                   const graphene_rect_t       *bounds,
-                                   const GskNglRenderOffscreen *offscreen)
-{
-  gsk_ngl_render_job_draw_offscreen_with_color (job, bounds, offscreen,
-                                                (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
-}
-
-/* load_float_vertex_data */
-static inline void
-gsk_ngl_render_job_draw_with_color (GskNglRenderJob *job,
-                                    float            x,
-                                    float            y,
-                                    float            width,
-                                    float            height,
-                                    guint16          color[4])
-{
-  float min_x = job->offset_x + x;
-  float min_y = job->offset_y + y;
-  float max_x = min_x + width;
-  float max_y = min_y + height;
-
-  gsk_ngl_render_job_draw_coords (job, min_x, min_y, max_x, max_y, 0, 0, 1, 1, color);
-}
-
-static inline void
-gsk_ngl_render_job_draw (GskNglRenderJob *job,
-                         float            x,
-                         float            y,
-                         float            width,
-                         float            height)
-{
-  gsk_ngl_render_job_draw_with_color (job, x, y, width, height,
-                                      (guint16[]) { FP_ZERO, FP_ZERO, FP_ZERO, FP_ZERO });
-}
-
-/* load_vertex_data */
-static inline void
-gsk_ngl_render_job_draw_rect_with_color (GskNglRenderJob       *job,
-                                         const graphene_rect_t *bounds,
-                                         guint16                color[4])
-{
-  gsk_ngl_render_job_draw_with_color (job,
-                                      bounds->origin.x,
-                                      bounds->origin.y,
-                                      bounds->size.width,
-                                      bounds->size.height,
-                                      color);
-}
-static inline void
-gsk_ngl_render_job_draw_rect (GskNglRenderJob       *job,
-                              const graphene_rect_t *bounds)
-{
-  gsk_ngl_render_job_draw (job,
-                           bounds->origin.x,
-                           bounds->origin.y,
-                           bounds->size.width,
-                           bounds->size.height);
-}
-
-/* load_offscreen_vertex_data */
-static inline void
-gsk_ngl_render_job_draw_offscreen_rect (GskNglRenderJob       *job,
-                                        const graphene_rect_t *bounds)
-{
-  float min_x = job->offset_x + bounds->origin.x;
-  float min_y = job->offset_y + bounds->origin.y;
-  float max_x = min_x + bounds->size.width;
-  float max_y = min_y + bounds->size.height;
-  guint16 color[4] = { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO };
-
-  gsk_ngl_render_job_draw_coords (job,
-                                  min_x, min_y, max_x, max_y,
-                                  0, 1, 1, 0,
-                                  color);
-}
-
-static inline void
-gsk_ngl_render_job_begin_draw (GskNglRenderJob *job,
-                               GskNglProgram   *program)
-{
-  job->current_program = program;
-
-  gsk_ngl_command_queue_begin_draw (job->command_queue,
-                                    program->program_info,
-                                    job->viewport.size.width,
-                                    job->viewport.size.height);
-
-  gsk_ngl_uniform_state_set4fv (program->uniforms,
-                                program->program_info,
-                                UNIFORM_SHARED_VIEWPORT,
-                                job->driver->stamps[UNIFORM_SHARED_VIEWPORT],
-                                1,
-                                (const float *)&job->viewport);
-
-  gsk_ngl_uniform_state_set_matrix (program->uniforms,
-                                    program->program_info,
-                                    UNIFORM_SHARED_MODELVIEW,
-                                    job->driver->stamps[UNIFORM_SHARED_MODELVIEW],
-                                    &job->current_modelview->matrix);
-
-  gsk_ngl_uniform_state_set_matrix (program->uniforms,
-                                    program->program_info,
-                                    UNIFORM_SHARED_PROJECTION,
-                                    job->driver->stamps[UNIFORM_SHARED_PROJECTION],
-                                    &job->projection);
-
-  gsk_ngl_uniform_state_set_rounded_rect (program->uniforms,
-                                          program->program_info,
-                                          UNIFORM_SHARED_CLIP_RECT,
-                                          job->driver->stamps[UNIFORM_SHARED_CLIP_RECT],
-                                          &job->current_clip->rect);
-
-  gsk_ngl_uniform_state_set1f (program->uniforms,
-                               program->program_info,
-                               UNIFORM_SHARED_ALPHA,
-                               job->driver->stamps[UNIFORM_SHARED_ALPHA],
-                               job->alpha);
-}
-
-#define CHOOSE_PROGRAM(job,name) \
-  (job->current_clip->is_fully_contained \
-      ? job->driver->name ## _no_clip \
-      : (job->current_clip->is_rectilinear \
-        ? job->driver->name ## _rect_clip \
-        : job->driver->name))
-
-static inline void
-gsk_ngl_render_job_split_draw (GskNglRenderJob *job)
-{
-  gsk_ngl_command_queue_split_draw (job->command_queue);
-}
-
-static inline void
-gsk_ngl_render_job_end_draw (GskNglRenderJob *job)
-{
-  gsk_ngl_command_queue_end_draw (job->command_queue);
-
-  job->current_program = NULL;
-}
-
-static inline void
-gsk_ngl_render_job_visit_as_fallback (GskNglRenderJob     *job,
-                                      const GskRenderNode *node)
-{
-  float scale_x = job->scale_x;
-  float scale_y = job->scale_y;
-  int surface_width = ceilf (node->bounds.size.width * scale_x);
-  int surface_height = ceilf (node->bounds.size.height * scale_y);
-  GdkTexture *texture;
-  cairo_surface_t *surface;
-  cairo_surface_t *rendered_surface;
-  cairo_t *cr;
-  int cached_id;
-  int texture_id;
-  GskTextureKey key;
-
-  if (surface_width <= 0 || surface_height <= 0)
-    return;
-
-  key.pointer = node;
-  key.pointer_is_child = FALSE;
-  key.scale_x = scale_x;
-  key.scale_y = scale_y;
-  key.filter = GL_NEAREST;
-
-  cached_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
-
-  if (cached_id != 0)
-    {
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D, GL_TEXTURE0, cached_id);
-      gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
-      gsk_ngl_render_job_end_draw (job);
-      return;
-    }
-
-  /* We first draw the recording surface on an image surface,
-   * just because the scaleY(-1) later otherwise screws up the
-   * rendering... */
-  {
-    rendered_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                                   surface_width,
-                                                   surface_height);
-
-    cairo_surface_set_device_scale (rendered_surface, scale_x, scale_y);
-    cr = cairo_create (rendered_surface);
-
-    cairo_save (cr);
-    cairo_translate (cr, - floorf (node->bounds.origin.x), - floorf (node->bounds.origin.y));
-    /* Render nodes don't modify state, so casting away the const is fine here */
-    gsk_render_node_draw ((GskRenderNode *)node, cr);
-    cairo_restore (cr);
-    cairo_destroy (cr);
-  }
-
-  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                        surface_width,
-                                        surface_height);
-  cairo_surface_set_device_scale (surface, scale_x, scale_y);
-  cr = cairo_create (surface);
-
-  /* We draw upside down here, so it matches what GL does. */
-  cairo_save (cr);
-  cairo_scale (cr, 1, -1);
-  cairo_translate (cr, 0, - surface_height / scale_y);
-  cairo_set_source_surface (cr, rendered_surface, 0, 0);
-  cairo_rectangle (cr, 0, 0, surface_width / scale_x, surface_height / scale_y);
-  cairo_fill (cr);
-  cairo_restore (cr);
-
-#ifdef G_ENABLE_DEBUG
-  if (job->debug_fallback)
-    {
-      cairo_move_to (cr, 0, 0);
-      cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height);
-      if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
-        cairo_set_source_rgba (cr, 0.3, 0, 1, 0.25);
-      else
-        cairo_set_source_rgba (cr, 1, 0, 0, 0.25);
-      cairo_fill_preserve (cr);
-      if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
-        cairo_set_source_rgba (cr, 0.3, 0, 1, 1);
-      else
-        cairo_set_source_rgba (cr, 1, 0, 0, 1);
-      cairo_stroke (cr);
-    }
-#endif
-  cairo_destroy (cr);
-
-  /* Create texture to upload */
-  texture = gdk_texture_new_for_surface (surface);
-  texture_id = gsk_ngl_driver_load_texture (job->driver, texture,
-                                            GL_NEAREST, GL_NEAREST);
-
-  if (gdk_gl_context_has_debug (job->command_queue->context))
-    gdk_gl_context_label_object_printf (job->command_queue->context, GL_TEXTURE, texture_id,
-                                        "Fallback %s %d",
-                                        g_type_name_from_instance ((GTypeInstance *) node),
-                                        texture_id);
-
-  g_object_unref (texture);
-  cairo_surface_destroy (surface);
-  cairo_surface_destroy (rendered_surface);
-
-  gsk_ngl_driver_cache_texture (job->driver, &key, texture_id);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       texture_id);
-  gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static guint
-blur_offscreen (GskNglRenderJob       *job,
-                GskNglRenderOffscreen *offscreen,
-                int                   texture_to_blur_width,
-                int                   texture_to_blur_height,
-                float                 blur_radius_x,
-                float                 blur_radius_y)
-{
-  const GskRoundedRect new_clip = GSK_ROUNDED_RECT_INIT (0, 0, texture_to_blur_width, texture_to_blur_height);
-  GskNglRenderTarget *pass1;
-  GskNglRenderTarget *pass2;
-  graphene_matrix_t prev_projection;
-  graphene_rect_t prev_viewport;
-  guint prev_fbo;
-
-  g_assert (blur_radius_x > 0);
-  g_assert (blur_radius_y > 0);
-  g_assert (offscreen->texture_id > 0);
-  g_assert (offscreen->area.x2 > offscreen->area.x);
-  g_assert (offscreen->area.y2 > offscreen->area.y);
-
-  if (!gsk_ngl_driver_create_render_target (job->driver,
-                                             MAX (texture_to_blur_width, 1),
-                                             MAX (texture_to_blur_height, 1),
-                                             job->target_format,
-                                             GL_NEAREST, GL_NEAREST,
-                                             &pass1))
-    return 0;
-
-  if (texture_to_blur_width <= 0 || texture_to_blur_height <= 0)
-    return gsk_ngl_driver_release_render_target (job->driver, pass1, FALSE);
-
-  if (!gsk_ngl_driver_create_render_target (job->driver,
-                                             texture_to_blur_width,
-                                             texture_to_blur_height,
-                                             job->target_format,
-                                             GL_NEAREST, GL_NEAREST,
-                                             &pass2))
-    return gsk_ngl_driver_release_render_target (job->driver, pass1, FALSE);
-
-  gsk_ngl_render_job_set_viewport (job, &new_clip.bounds, &prev_viewport);
-  gsk_ngl_render_job_set_projection_from_rect (job, &new_clip.bounds, &prev_projection);
-  gsk_ngl_render_job_set_modelview (job, NULL);
-  gsk_ngl_render_job_push_clip (job, &new_clip);
-
-  /* Bind new framebuffer and clear it */
-  prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, pass1->framebuffer_id);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-  /* Begin drawing the first horizontal pass, using offscreen as the
-   * source texture for the program.
-   */
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       offscreen->texture_id);
-  gsk_ngl_program_set_uniform1f (job->current_program,
-                                 UNIFORM_BLUR_RADIUS, 0,
-                                 blur_radius_x);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_BLUR_SIZE, 0,
-                                 texture_to_blur_width,
-                                 texture_to_blur_height);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_BLUR_DIR, 0,
-                                 1, 0);
-  gsk_ngl_render_job_draw_coords (job,
-                                  0, 0, texture_to_blur_width, texture_to_blur_height,
-                                  0, 1, 1, 0,
-                                  (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
-  gsk_ngl_render_job_end_draw (job);
-
-  /* Bind second pass framebuffer and clear it */
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, pass2->framebuffer_id);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-  /* Draw using blur program with first pass as source texture */
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       pass1->texture_id);
-  gsk_ngl_program_set_uniform1f (job->current_program,
-                                 UNIFORM_BLUR_RADIUS, 0,
-                                 blur_radius_y);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_BLUR_SIZE, 0,
-                                 texture_to_blur_width,
-                                 texture_to_blur_height);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_BLUR_DIR, 0,
-                                 0, 1);
-  gsk_ngl_render_job_draw_coords (job,
-                                  0, 0, texture_to_blur_width, texture_to_blur_height,
-                                  0, 1, 1, 0,
-                                  (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
-  gsk_ngl_render_job_end_draw (job);
-
-  gsk_ngl_render_job_pop_modelview (job);
-  gsk_ngl_render_job_pop_clip (job);
-  gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
-  gsk_ngl_render_job_set_projection (job, &prev_projection);
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
-
-  gsk_ngl_driver_release_render_target (job->driver, pass1, TRUE);
-
-  return gsk_ngl_driver_release_render_target (job->driver, pass2, FALSE);
-}
-
-static void
-blur_node (GskNglRenderJob       *job,
-           GskNglRenderOffscreen *offscreen,
-           const GskRenderNode  *node,
-           float                 blur_radius,
-           float                *min_x,
-           float                *max_x,
-           float                *min_y,
-           float                *max_y)
-{
-  const float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
-  const float half_blur_extra = (blur_extra / 2.0);
-  float scale_x = job->scale_x;
-  float scale_y = job->scale_y;
-  float texture_width;
-  float texture_height;
-
-  g_assert (blur_radius > 0);
-
-  /* Increase texture size for the given blur radius and scale it */
-  texture_width  = ceilf ((node->bounds.size.width  + blur_extra));
-  texture_height = ceilf ((node->bounds.size.height + blur_extra));
-
-  /* Only blur this if the out region has no texture id yet */
-  if (offscreen->texture_id == 0)
-    {
-      const graphene_rect_t bounds = GRAPHENE_RECT_INIT (node->bounds.origin.x - half_blur_extra,
-                                                         node->bounds.origin.y - half_blur_extra,
-                                                         texture_width, texture_height);
-
-      offscreen->bounds = &bounds;
-      offscreen->reset_clip = TRUE;
-      offscreen->force_offscreen = TRUE;
-
-      if (!gsk_ngl_render_job_visit_node_with_offscreen (job, node, offscreen))
-        g_assert_not_reached ();
-
-      /* Ensure that we actually got a real texture_id */
-      g_assert (offscreen->texture_id != 0);
-
-      offscreen->texture_id = blur_offscreen (job,
-                                              offscreen,
-                                              texture_width * scale_x,
-                                              texture_height * scale_y,
-                                              blur_radius * scale_x,
-                                              blur_radius * scale_y);
-      init_full_texture_region (offscreen);
-    }
-
-  *min_x = job->offset_x + node->bounds.origin.x - half_blur_extra;
-  *max_x = job->offset_x + node->bounds.origin.x + node->bounds.size.width + half_blur_extra;
-  *min_y = job->offset_y + node->bounds.origin.y - half_blur_extra;
-  *max_y = job->offset_y + node->bounds.origin.y + node->bounds.size.height + half_blur_extra;
-}
-
-#define ATLAS_SIZE 512
-
-static inline void
-gsk_ngl_render_job_visit_color_node (GskNglRenderJob     *job,
-                                     const GskRenderNode *node)
-{
-  const GdkRGBA *rgba;
-  guint16 color[4];
-  GskNglProgram *program;
-  GskNglCommandBatch *batch;
-
-  rgba = gsk_color_node_get_color (node);
-  if (RGBA_IS_CLEAR (rgba))
-    return;
-
-  rgba_to_half (rgba, color);
-
-  /* Avoid switching away from the coloring program for
-   * rendering a solid color.
-   */
-  program = CHOOSE_PROGRAM (job, coloring);
-  batch = gsk_ngl_command_queue_get_batch (job->command_queue);
-
-  /* Limit the size, or we end up with a coordinate overflow somwhere. */
-  if (node->bounds.size.width < 300 &&
-      node->bounds.size.height < 300 &&
-      batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW &&
-      batch->any.program == program->id)
-    {
-      GskNglRenderOffscreen offscreen = {0};
-
-      gsk_ngl_render_job_begin_draw (job, program);
-
-      /* The top left few pixels in our atlases are always
-       * solid white, so we can use it here, without
-       * having to choose any particular atlas texture.
-       */
-      offscreen.was_offscreen = FALSE;
-      offscreen.area.x = 1.f / ATLAS_SIZE;
-      offscreen.area.y = 1.f / ATLAS_SIZE;
-      offscreen.area.x2 = 2.f / ATLAS_SIZE;
-      offscreen.area.y2 = 2.f / ATLAS_SIZE;
-
-      gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                    &node->bounds,
-                                                    &offscreen,
-                                                    color);
-
-      gsk_ngl_render_job_end_draw (job);
-    }
-  else
-    {
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
-      gsk_ngl_render_job_draw_rect_with_color (job, &node->bounds, color);
-      gsk_ngl_render_job_end_draw (job);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_linear_gradient_node (GskNglRenderJob     *job,
-                                               const GskRenderNode *node)
-{
-  const GskColorStop *stops = gsk_linear_gradient_node_get_color_stops (node, NULL);
-  const graphene_point_t *start = gsk_linear_gradient_node_get_start (node);
-  const graphene_point_t *end = gsk_linear_gradient_node_get_end (node);
-  int n_color_stops = gsk_linear_gradient_node_get_n_color_stops (node);
-  gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE;
-  float x1 = job->offset_x + start->x;
-  float x2 = job->offset_x + end->x;
-  float y1 = job->offset_y + start->y;
-  float y2 = job->offset_y + end->y;
-
-  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, linear_gradient));
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_LINEAR_GRADIENT_NUM_COLOR_STOPS, 0,
-                                 n_color_stops);
-  gsk_ngl_program_set_uniform1fv (job->current_program,
-                                  UNIFORM_LINEAR_GRADIENT_COLOR_STOPS, 0,
-                                  n_color_stops * 5,
-                                  (const float *)stops);
-  gsk_ngl_program_set_uniform4f (job->current_program,
-                                 UNIFORM_LINEAR_GRADIENT_POINTS, 0,
-                                 x1, y1, x2 - x1, y2 - y1);
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_LINEAR_GRADIENT_REPEAT, 0,
-                                 repeat);
-  gsk_ngl_render_job_draw_rect (job, &node->bounds);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_conic_gradient_node (GskNglRenderJob     *job,
-                                              const GskRenderNode *node)
-{
-  static const float scale = 0.5f * M_1_PI;
-
-  const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, NULL);
-  const graphene_point_t *center = gsk_conic_gradient_node_get_center (node);
-  int n_color_stops = gsk_conic_gradient_node_get_n_color_stops (node);
-  float angle = gsk_conic_gradient_node_get_angle (node);
-  float bias = angle * scale + 2.0f;
-
-  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, conic_gradient));
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_CONIC_GRADIENT_NUM_COLOR_STOPS, 0,
-                                 n_color_stops);
-  gsk_ngl_program_set_uniform1fv (job->current_program,
-                                  UNIFORM_CONIC_GRADIENT_COLOR_STOPS, 0,
-                                  n_color_stops * 5,
-                                  (const float *)stops);
-  gsk_ngl_program_set_uniform4f (job->current_program,
-                                 UNIFORM_CONIC_GRADIENT_GEOMETRY, 0,
-                                 job->offset_x + center->x,
-                                 job->offset_y + center->y,
-                                 scale,
-                                 bias);
-  gsk_ngl_render_job_draw_rect (job, &node->bounds);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_radial_gradient_node (GskNglRenderJob     *job,
-                                               const GskRenderNode *node)
-{
-  int n_color_stops = gsk_radial_gradient_node_get_n_color_stops (node);
-  const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, NULL);
-  const graphene_point_t *center = gsk_radial_gradient_node_get_center (node);
-  float start = gsk_radial_gradient_node_get_start (node);
-  float end = gsk_radial_gradient_node_get_end (node);
-  float hradius = gsk_radial_gradient_node_get_hradius (node);
-  float vradius = gsk_radial_gradient_node_get_vradius (node);
-  gboolean repeat = gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE;
-  float scale = 1.0f / (end - start);
-  float bias = -start * scale;
-
-  g_assert (n_color_stops < MAX_GRADIENT_STOPS);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, radial_gradient));
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_RADIAL_GRADIENT_NUM_COLOR_STOPS, 0,
-                                 n_color_stops);
-  gsk_ngl_program_set_uniform1fv (job->current_program,
-                                  UNIFORM_RADIAL_GRADIENT_COLOR_STOPS, 0,
-                                  n_color_stops * 5,
-                                  (const float *)stops);
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_RADIAL_GRADIENT_REPEAT, 0,
-                                 repeat);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_RADIAL_GRADIENT_RANGE, 0,
-                                 scale, bias);
-  gsk_ngl_program_set_uniform4f (job->current_program,
-                                 UNIFORM_RADIAL_GRADIENT_GEOMETRY, 0,
-                                 job->offset_x + center->x,
-                                 job->offset_y + center->y,
-                                 1.0f / (hradius * job->scale_x),
-                                 1.0f / (vradius * job->scale_y));
-  gsk_ngl_render_job_draw_rect (job, &node->bounds);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_clipped_child (GskNglRenderJob       *job,
-                                        const GskRenderNode   *child,
-                                        const graphene_rect_t *clip)
-{
-  graphene_rect_t transformed_clip;
-  GskRoundedRect intersection;
-
-  gsk_ngl_render_job_transform_bounds (job, clip, &transformed_clip);
-
-  if (job->current_clip->is_rectilinear)
-    {
-      memset (&intersection.corner, 0, sizeof intersection.corner);
-      graphene_rect_intersection (&transformed_clip,
-                                  &job->current_clip->rect.bounds,
-                                  &intersection.bounds);
-
-      gsk_ngl_render_job_push_clip (job, &intersection);
-      gsk_ngl_render_job_visit_node (job, child);
-      gsk_ngl_render_job_pop_clip (job);
-    }
-  else if (intersect_rounded_rectilinear (&transformed_clip,
-                                          &job->current_clip->rect,
-                                          &intersection))
-    {
-      gsk_ngl_render_job_push_clip (job, &intersection);
-      gsk_ngl_render_job_visit_node (job, child);
-      gsk_ngl_render_job_pop_clip (job);
-    }
-  else
-    {
-      GskNglRenderOffscreen offscreen = {0};
-
-      offscreen.bounds = clip;
-      offscreen.force_offscreen = TRUE;
-      offscreen.reset_clip = TRUE;
-      offscreen.do_not_cache = TRUE;
-
-      gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen);
-
-      g_assert (offscreen.texture_id);
-
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           offscreen.texture_id);
-      gsk_ngl_render_job_draw_offscreen_rect (job, clip);
-      gsk_ngl_render_job_end_draw (job);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_clip_node (GskNglRenderJob     *job,
-                                    const GskRenderNode *node)
-{
-  const graphene_rect_t *clip = gsk_clip_node_get_clip (node);
-  const GskRenderNode *child = gsk_clip_node_get_child (node);
-
-  gsk_ngl_render_job_visit_clipped_child (job, child, clip);
-}
-
-static inline void
-gsk_ngl_render_job_visit_rounded_clip_node (GskNglRenderJob     *job,
-                                            const GskRenderNode *node)
-{
-  const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
-  const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node);
-  GskRoundedRect transformed_clip;
-  float scale_x = job->scale_x;
-  float scale_y = job->scale_y;
-  gboolean need_offscreen;
-
-  if (node_is_invisible (child))
-    return;
-
-  gsk_ngl_render_job_transform_bounds (job, &clip->bounds, &transformed_clip.bounds);
-
-  for (guint i = 0; i < G_N_ELEMENTS (transformed_clip.corner); i++)
-    {
-      transformed_clip.corner[i].width = clip->corner[i].width * scale_x;
-      transformed_clip.corner[i].height = clip->corner[i].height * scale_y;
-    }
-
-  if (job->current_clip->is_rectilinear)
-    {
-      GskRoundedRect intersected_clip;
-
-      if (intersect_rounded_rectilinear (&job->current_clip->rect.bounds,
-                                         &transformed_clip,
-                                         &intersected_clip))
-        {
-          gsk_ngl_render_job_push_clip (job, &intersected_clip);
-          gsk_ngl_render_job_visit_node (job, child);
-          gsk_ngl_render_job_pop_clip (job);
-          return;
-        }
-    }
-
-  /* After this point we are really working with a new and a current clip
-   * which both have rounded corners.
-   */
-
-  if (job->clip->len <= 1)
-    need_offscreen = FALSE;
-  else if (rounded_inner_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds))
-    need_offscreen = FALSE;
-  else
-    need_offscreen = TRUE;
-
-  if (!need_offscreen)
-    {
-      /* If the new clip entirely contains the current clip, the intersection is simply
-       * the current clip, so we can ignore the new one.
-       */
-      if (rounded_inner_rect_contains_rect (&transformed_clip, &job->current_clip->rect.bounds))
-        {
-          gsk_ngl_render_job_visit_node (job, child);
-          return;
-        }
-
-      gsk_ngl_render_job_push_clip (job, &transformed_clip);
-      gsk_ngl_render_job_visit_node (job, child);
-      gsk_ngl_render_job_pop_clip (job);
-    }
-  else
-    {
-      GskNglRenderOffscreen offscreen = {0};
-
-      offscreen.bounds = &node->bounds;
-      offscreen.force_offscreen = TRUE;
-      offscreen.reset_clip = FALSE;
-
-      gsk_ngl_render_job_push_clip (job, &transformed_clip);
-      if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
-        g_assert_not_reached ();
-      gsk_ngl_render_job_pop_clip (job);
-
-      g_assert (offscreen.texture_id);
-
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           offscreen.texture_id);
-      gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-      gsk_ngl_render_job_end_draw (job);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_rect_border_node (GskNglRenderJob     *job,
-                                           const GskRenderNode *node)
-{
-  const GdkRGBA *colors = gsk_border_node_get_colors (node);
-  const float *widths = gsk_border_node_get_widths (node);
-  const graphene_point_t *origin = &node->bounds.origin;
-  const graphene_size_t *size = &node->bounds.size;
-  guint16 color[4];
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
-
-  if (widths[0] > 0)
-    {
-      rgba_to_half (&colors[0], color);
-      gsk_ngl_render_job_draw_rect_with_color (job,
-                                               &GRAPHENE_RECT_INIT (origin->x, origin->y, size->width - widths[1], widths[0]),
-                                               color);
-    }
-
-  if (widths[1] > 0)
-    {
-      rgba_to_half (&colors[1], color);
-      gsk_ngl_render_job_draw_rect_with_color (job,
-                                               &GRAPHENE_RECT_INIT (origin->x + size->width - widths[1], origin->y, widths[1], size->height - widths[2]),
-                                               color);
-    }
-
-  if (widths[2] > 0)
-    {
-      rgba_to_half (&colors[2], color);
-      gsk_ngl_render_job_draw_rect_with_color (job,
-                                               &GRAPHENE_RECT_INIT (origin->x + widths[3], origin->y + size->height - widths[2], size->width - widths[3], widths[2]),
-                                               color);
-    }
-
-  if (widths[3] > 0)
-    {
-      rgba_to_half (&colors[3], color);
-      gsk_ngl_render_job_draw_rect_with_color (job,
-                                               &GRAPHENE_RECT_INIT (origin->x, origin->y + widths[0], widths[3], size->height - widths[0]),
-                                               color);
-    }
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_border_node (GskNglRenderJob     *job,
-                                      const GskRenderNode *node)
-{
-  const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node);
-  const GdkRGBA *colors = gsk_border_node_get_colors (node);
-  const float *widths = gsk_border_node_get_widths (node);
-  struct {
-    float w;
-    float h;
-  } sizes[4];
-  float min_x = job->offset_x + node->bounds.origin.x;
-  float min_y = job->offset_y + node->bounds.origin.y;
-  float max_x = min_x + node->bounds.size.width;
-  float max_y = min_y + node->bounds.size.height;
-  GskRoundedRect outline;
-  guint16 color[4];
-
-  memset (sizes, 0, sizeof sizes);
-
-  if (widths[0] > 0)
-    {
-      sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height);
-      sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height);
-    }
-
-  if (widths[1] > 0)
-    {
-      sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width);
-      sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width);
-    }
-
-  if (widths[2] > 0)
-    {
-      sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height);
-      sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height);
-    }
-
-  if (widths[3] > 0)
-    {
-      sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width);
-      sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width);
-    }
-
-  gsk_ngl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, border));
-
-  gsk_ngl_program_set_uniform4fv (job->current_program,
-                                  UNIFORM_BORDER_WIDTHS, 0,
-                                  1,
-                                  widths);
-  gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                            UNIFORM_BORDER_OUTLINE_RECT, 0,
-                                            &outline);
-
-  if (widths[0] > 0)
-    {
-      GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-      rgba_to_half (&colors[0], color);
-
-      vertices[0] = (GskNglDrawVertex) { .position = { min_x,              min_y              }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[1] = (GskNglDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[2] = (GskNglDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-
-      vertices[3] = (GskNglDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[4] = (GskNglDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[5] = (GskNglDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-    }
-
-  if (widths[1] > 0)
-    {
-      GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-      rgba_to_half (&colors[1], color);
-
-      vertices[0] = (GskNglDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[1] = (GskNglDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[2] = (GskNglDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-
-      vertices[3] = (GskNglDrawVertex) { .position = { max_x,              max_y              }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[4] = (GskNglDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[5] = (GskNglDrawVertex) { .position = { max_x,              min_y              }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-    }
-
-  if (widths[2] > 0)
-    {
-      GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-      rgba_to_half (&colors[2], color);
-
-      vertices[0] = (GskNglDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[1] = (GskNglDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[2] = (GskNglDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-
-      vertices[3] = (GskNglDrawVertex) { .position = { max_x,              max_y              }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[4] = (GskNglDrawVertex) { .position = { min_x            ,  max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[5] = (GskNglDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-    }
-
-  if (widths[3] > 0)
-    {
-      GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-      rgba_to_half (&colors[3], color);
-
-      vertices[0] = (GskNglDrawVertex) { .position = { min_x,              min_y              }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[1] = (GskNglDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[2] = (GskNglDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-
-      vertices[3] = (GskNglDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[4] = (GskNglDrawVertex) { .position = { min_x,              max_y              }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } };
-      vertices[5] = (GskNglDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } };
-    }
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-/* A special case for a pattern that occurs frequently with CSS
- * backgrounds: two sibling nodes, the first of which is a rounded
- * clip node with a color node as child, and the second one is a
- * border node, with the same outline as the clip node. We render
- * this using the filled_border shader.
- */
-static void
-gsk_ngl_render_job_visit_css_background (GskNglRenderJob     *job,
-                                         const GskRenderNode *node,
-                                         const GskRenderNode *node2)
-{
-  const GskRenderNode *child = gsk_rounded_clip_node_get_child (node);
-  const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node2);
-  const float *widths = gsk_border_node_get_widths (node2);
-  float min_x = job->offset_x + node2->bounds.origin.x;
-  float min_y = job->offset_y + node2->bounds.origin.y;
-  float max_x = min_x + node2->bounds.size.width;
-  float max_y = min_y + node2->bounds.size.height;
-  GskRoundedRect outline;
-  GskNglDrawVertex *vertices;
-  guint16 color[4];
-  guint16 color2[4];
-
-  if (node_is_invisible (node2))
-    return;
-
-  rgba_to_half (&gsk_border_node_get_colors (node2)[0], color);
-  rgba_to_half (gsk_color_node_get_color (child), color2);
-
-  gsk_ngl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border));
-
-  gsk_ngl_program_set_uniform4fv (job->current_program,
-                                  UNIFORM_FILLED_BORDER_WIDTHS, 0,
-                                  1,
-                                  widths);
-  gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                            UNIFORM_FILLED_BORDER_OUTLINE_RECT, 0,
-                                            &outline);
-
-  vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
-
-  vertices[0] = (GskNglDrawVertex) { .position = { min_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-  vertices[1] = (GskNglDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-  vertices[2] = (GskNglDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-  vertices[3] = (GskNglDrawVertex) { .position = { max_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-  vertices[4] = (GskNglDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-  vertices[5] = (GskNglDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } };
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-/* Returns TRUE if applying @transform to @bounds
- * yields an axis-aligned rectangle
- */
-static gboolean
-result_is_axis_aligned (GskTransform          *transform,
-                        const graphene_rect_t *bounds)
-{
-  graphene_matrix_t m;
-  graphene_quad_t q;
-  graphene_rect_t b;
-  graphene_point_t b1, b2;
-  const graphene_point_t *p;
-
-  gsk_transform_to_matrix (transform, &m);
-  gsk_matrix_transform_rect (&m, bounds, &q);
-  graphene_quad_bounds (&q, &b);
-  graphene_rect_get_top_left (&b, &b1);
-  graphene_rect_get_bottom_right (&b, &b2);
-
-  for (guint i = 0; i < 4; i++)
-    {
-      p = graphene_quad_get_point (&q, i);
-      if (fabs (p->x - b1.x) > FLT_EPSILON && fabs (p->x - b2.x) > FLT_EPSILON)
-        return FALSE;
-      if (fabs (p->y - b1.y) > FLT_EPSILON && fabs (p->y - b2.y) > FLT_EPSILON)
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-static inline void
-gsk_ngl_render_job_visit_transform_node (GskNglRenderJob     *job,
-                                         const GskRenderNode *node)
-{
-  GskTransform *transform = gsk_transform_node_get_transform (node);
-  const GskTransformCategory category = gsk_transform_get_category (transform);
-  const GskRenderNode *child = gsk_transform_node_get_child (node);
-
-  switch (category)
-    {
-    case GSK_TRANSFORM_CATEGORY_IDENTITY:
-      gsk_ngl_render_job_visit_node (job, child);
-    break;
-
-    case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
-      {
-        float dx, dy;
-
-        gsk_transform_node_get_translate (node, &dx, &dy);
-        gsk_ngl_render_job_offset (job, dx, dy);
-        gsk_ngl_render_job_visit_node (job, child);
-        gsk_ngl_render_job_offset (job, -dx, -dy);
-      }
-    break;
-
-    case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
-      {
-        gsk_ngl_render_job_push_modelview (job, transform);
-        gsk_ngl_render_job_visit_node (job, child);
-        gsk_ngl_render_job_pop_modelview (job);
-      }
-    break;
-
-    case GSK_TRANSFORM_CATEGORY_2D:
-    case GSK_TRANSFORM_CATEGORY_3D:
-    case GSK_TRANSFORM_CATEGORY_ANY:
-    case GSK_TRANSFORM_CATEGORY_UNKNOWN:
-      if (node_supports_transform (child))
-        {
-          gsk_ngl_render_job_push_modelview (job, transform);
-          gsk_ngl_render_job_visit_node (job, child);
-          gsk_ngl_render_job_pop_modelview (job);
-        }
-      else
-        {
-          GskNglRenderOffscreen offscreen = {0};
-          float sx = 1, sy  = 1;
-
-          offscreen.bounds = &child->bounds;
-          offscreen.force_offscreen = FALSE;
-          offscreen.reset_clip = TRUE;
-
-          if (!result_is_axis_aligned (transform, &child->bounds))
-            offscreen.linear_filter = TRUE;
-
-          if (category == GSK_TRANSFORM_CATEGORY_2D)
-            {
-              graphene_matrix_t m;
-              double a, b, c, d, tx, ty;
-
-              g_assert (transform != NULL);
-              gsk_transform_to_matrix (transform, &m);
-              if (graphene_matrix_to_2d (&m, &a, &b, &c, &d, &tx, &ty))
-                {
-                  sx = sqrt (a * a + b * b);
-                  sy = sqrt (c * c + d * d);
-                }
-              else
-                sx = sy = 1;
-
-              if (sx != 1 || sy != 1)
-                {
-                  GskTransform *scale;
-
-                  scale = gsk_transform_translate (gsk_transform_scale (NULL, sx, sy), &GRAPHENE_POINT_INIT (tx, ty));
-                  gsk_ngl_render_job_push_modelview (job, scale);
-                  transform = gsk_transform_transform (gsk_transform_invert (scale), transform);
-                }
-            }
-
-          if (gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
-            {
-              /* For non-trivial transforms, we draw everything on a texture and then
-               * draw the texture transformed. */
-              if (transform)
-                gsk_ngl_render_job_push_modelview (job, transform);
-
-              gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-              gsk_ngl_program_set_uniform_texture (job->current_program,
-                                                   UNIFORM_SHARED_SOURCE, 0,
-                                                   GL_TEXTURE_2D,
-                                                   GL_TEXTURE0,
-                                                   offscreen.texture_id);
-              gsk_ngl_render_job_draw_offscreen (job, &child->bounds, &offscreen);
-              gsk_ngl_render_job_end_draw (job);
-
-              if (transform)
-                gsk_ngl_render_job_pop_modelview (job);
-            }
-
-          if (category == GSK_TRANSFORM_CATEGORY_2D)
-            {
-              if (sx != 1 || sy != 1)
-                {
-                  gsk_ngl_render_job_pop_modelview (job);
-                  gsk_transform_unref (transform);
-                }
-            }
-        }
-    break;
-
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_unblurred_inset_shadow_node (GskNglRenderJob     *job,
-                                                      const GskRenderNode *node)
-{
-  const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node);
-  GskRoundedRect transformed_outline;
-  guint16 color[4];
-
-  gsk_ngl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
-  gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                            UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
-                                            &transformed_outline);
-  gsk_ngl_program_set_uniform1f (job->current_program,
-                                 UNIFORM_INSET_SHADOW_SPREAD, 0,
-                                 gsk_inset_shadow_node_get_spread (node));
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_INSET_SHADOW_OFFSET, 0,
-                                 gsk_inset_shadow_node_get_dx (node),
-                                 gsk_inset_shadow_node_get_dy (node));
-  rgba_to_half (gsk_inset_shadow_node_get_color (node), color);
-  gsk_ngl_render_job_draw_rect_with_color (job, &node->bounds, color);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_blurred_inset_shadow_node (GskNglRenderJob     *job,
-                                                    const GskRenderNode *node)
-{
-  const GskRoundedRect *node_outline = gsk_inset_shadow_node_get_outline (node);
-  float blur_radius = gsk_inset_shadow_node_get_blur_radius (node);
-  float offset_x = gsk_inset_shadow_node_get_dx (node);
-  float offset_y = gsk_inset_shadow_node_get_dy (node);
-  float scale_x = job->scale_x;
-  float scale_y = job->scale_y;
-  float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */
-  float half_blur_extra = blur_radius;
-  float texture_width;
-  float texture_height;
-  int blurred_texture_id;
-  GskTextureKey key;
-  GskNglRenderOffscreen offscreen = {0};
-  guint16 color[4];
-
-  g_assert (blur_radius > 0);
-
-  texture_width = ceilf ((node_outline->bounds.size.width + blur_extra) * scale_x);
-  texture_height = ceilf ((node_outline->bounds.size.height + blur_extra) * scale_y);
-
-  key.pointer = node;
-  key.pointer_is_child = FALSE;
-  key.scale_x = scale_x;
-  key.scale_y = scale_y;
-  key.filter = GL_NEAREST;
-
-  blurred_texture_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
-
-  if (blurred_texture_id == 0)
-    {
-      float spread = gsk_inset_shadow_node_get_spread (node) + half_blur_extra;
-      GskRoundedRect transformed_outline;
-      GskRoundedRect outline_to_blur;
-      GskNglRenderTarget *render_target;
-      graphene_matrix_t prev_projection;
-      graphene_rect_t prev_viewport;
-      guint prev_fbo;
-
-      /* TODO: In the following code, we have to be careful about where we apply the scale.
-       * We're manually scaling stuff (e.g. the outline) so we can later use texture_width
-       * and texture_height (which are already scaled) as the geometry and keep the modelview
-       * at a scale of 1. That's kinda complicated though... */
-
-      /* Outline of what we actually want to blur later.
-       * Spread grows inside, so we don't need to account for that. But the blur will need
-       * to read outside of the inset shadow, so we need to draw some color in there. */
-      outline_to_blur = *node_outline;
-      gsk_rounded_rect_shrink (&outline_to_blur,
-                               -half_blur_extra,
-                               -half_blur_extra,
-                               -half_blur_extra,
-                               -half_blur_extra);
-
-      /* Fit to our texture */
-      outline_to_blur.bounds.origin.x = 0;
-      outline_to_blur.bounds.origin.y = 0;
-      outline_to_blur.bounds.size.width *= scale_x;
-      outline_to_blur.bounds.size.height *= scale_y;
-
-      for (guint i = 0; i < 4; i ++)
-        {
-          outline_to_blur.corner[i].width *= scale_x;
-          outline_to_blur.corner[i].height *= scale_y;
-        }
-
-      if (!gsk_ngl_driver_create_render_target (job->driver,
-                                                 texture_width, texture_height,
-                                                 get_target_format (job, node),
-                                                 GL_NEAREST, GL_NEAREST,
-                                                 &render_target))
-        g_assert_not_reached ();
-
-      gsk_ngl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
-      gsk_ngl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
-      gsk_ngl_render_job_set_modelview (job, NULL);
-      gsk_ngl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT (0, 0, texture_width, texture_height));
-
-      prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
-      gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-      gsk_ngl_render_job_transform_rounded_rect (job, &outline_to_blur, &transformed_outline);
-
-      /* Actual inset shadow outline drawing */
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow));
-      gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                                UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
-                                                &transformed_outline);
-      gsk_ngl_program_set_uniform1f (job->current_program,
-                                     UNIFORM_INSET_SHADOW_SPREAD, 0,
-                                     spread * MAX (scale_x, scale_y));
-      gsk_ngl_program_set_uniform2f (job->current_program,
-                                     UNIFORM_INSET_SHADOW_OFFSET, 0,
-                                     offset_x * scale_x,
-                                     offset_y * scale_y);
-      rgba_to_half (gsk_inset_shadow_node_get_color (node), color);
-      gsk_ngl_render_job_draw_with_color (job,
-                                          0, 0, texture_width, texture_height,
-                                          color);
-      gsk_ngl_render_job_end_draw (job);
-
-      gsk_ngl_render_job_pop_modelview (job);
-      gsk_ngl_render_job_pop_clip (job);
-      gsk_ngl_render_job_set_projection (job, &prev_projection);
-      gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
-      gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
-
-      offscreen.texture_id = render_target->texture_id;
-      init_full_texture_region (&offscreen);
-
-      blurred_texture_id = blur_offscreen (job,
-                                           &offscreen,
-                                           texture_width,
-                                           texture_height,
-                                           blur_radius * scale_x,
-                                           blur_radius * scale_y);
-
-      gsk_ngl_driver_release_render_target (job->driver, render_target, TRUE);
-
-      gsk_ngl_driver_cache_texture (job->driver, &key, blurred_texture_id);
-    }
-
-  g_assert (blurred_texture_id != 0);
-
-  /* Blur the rendered unblurred inset shadow */
-  /* Use a clip to cut away the unwanted parts outside of the original outline */
-  {
-    const gboolean needs_clip = !gsk_rounded_rect_is_rectilinear (node_outline);
-    const float tx1 = half_blur_extra * scale_x / texture_width;
-    const float tx2 = 1.0 - tx1;
-    const float ty1 = half_blur_extra * scale_y / texture_height;
-    const float ty2 = 1.0 - ty1;
-
-    if (needs_clip)
-      {
-        GskRoundedRect node_clip;
-
-        gsk_ngl_render_job_transform_bounds (job, &node_outline->bounds, &node_clip.bounds);
-
-        for (guint i = 0; i < 4; i ++)
-          {
-            node_clip.corner[i].width = node_outline->corner[i].width * scale_x;
-            node_clip.corner[i].height = node_outline->corner[i].height * scale_y;
-          }
-
-        gsk_ngl_render_job_push_clip (job, &node_clip);
-      }
-
-    offscreen.was_offscreen = TRUE;
-    offscreen.area.x = tx1;
-    offscreen.area.y = ty1;
-    offscreen.area.x2 = tx2;
-    offscreen.area.y2 = ty2;
-
-    gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-    gsk_ngl_program_set_uniform_texture (job->current_program,
-                                         UNIFORM_SHARED_SOURCE, 0,
-                                         GL_TEXTURE_2D,
-                                         GL_TEXTURE0,
-                                         blurred_texture_id);
-    gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-    gsk_ngl_render_job_end_draw (job);
-
-    if (needs_clip)
-      gsk_ngl_render_job_pop_clip (job);
-  }
-}
-
-static inline void
-gsk_ngl_render_job_visit_unblurred_outset_shadow_node (GskNglRenderJob     *job,
-                                                       const GskRenderNode *node)
-{
-  const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
-  GskRoundedRect transformed_outline;
-  float x = node->bounds.origin.x;
-  float y = node->bounds.origin.y;
-  float w = node->bounds.size.width;
-  float h = node->bounds.size.height;
-  float spread = gsk_outset_shadow_node_get_spread (node);
-  float dx = gsk_outset_shadow_node_get_dx (node);
-  float dy = gsk_outset_shadow_node_get_dy (node);
-  guint16 color[4];
-  const float edge_sizes[] = { // Top, right, bottom, left
-    spread - dy, spread + dx, spread + dy, spread - dx
-  };
-  const float corner_sizes[][2] = { // top left, top right, bottom right, bottom left
-    { outline->corner[0].width + spread - dx, outline->corner[0].height + spread - dy },
-    { outline->corner[1].width + spread + dx, outline->corner[1].height + spread - dy },
-    { outline->corner[2].width + spread + dx, outline->corner[2].height + spread + dy },
-    { outline->corner[3].width + spread - dx, outline->corner[3].height + spread + dy },
-  };
-
-  rgba_to_half (gsk_outset_shadow_node_get_color (node), color);
-
-  gsk_ngl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, unblurred_outset_shadow));
-  gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                            UNIFORM_UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, 0,
-                                            &transformed_outline);
-  gsk_ngl_program_set_uniform1f (job->current_program,
-                                 UNIFORM_UNBLURRED_OUTSET_SHADOW_SPREAD, 0,
-                                 spread);
-  gsk_ngl_program_set_uniform2f (job->current_program,
-                                 UNIFORM_UNBLURRED_OUTSET_SHADOW_OFFSET, 0,
-                                 dx, dy);
-
-  /* Corners... */
-  if (corner_sizes[0][0] > 0 && corner_sizes[0][1] > 0) /* Top left */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x, y, corner_sizes[0][0], corner_sizes[0][1],
-                                        color);
-  if (corner_sizes[1][0] > 0 && corner_sizes[1][1] > 0) /* Top right */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x + w - corner_sizes[1][0], y,
-                                        corner_sizes[1][0], corner_sizes[1][1],
-                                        color);
-  if (corner_sizes[2][0] > 0 && corner_sizes[2][1] > 0) /* Bottom right */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x + w - corner_sizes[2][0], y + h - corner_sizes[2][1],
-                                        corner_sizes[2][0], corner_sizes[2][1],
-                                        color);
-  if (corner_sizes[3][0] > 0 && corner_sizes[3][1] > 0) /* Bottom left */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x, y + h - corner_sizes[3][1],
-                                        corner_sizes[3][0], corner_sizes[3][1],
-                                        color);
-  /* Edges... */;
-  if (edge_sizes[0] > 0) /* Top */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x + corner_sizes[0][0], y,
-                                        w - corner_sizes[0][0] - corner_sizes[1][0], edge_sizes[0],
-                                        color);
-  if (edge_sizes[1] > 0) /* Right */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x + w - edge_sizes[1], y + corner_sizes[1][1],
-                                        edge_sizes[1], h - corner_sizes[1][1] - corner_sizes[2][1],
-                                        color);
-  if (edge_sizes[2] > 0) /* Bottom */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x + corner_sizes[3][0], y + h - edge_sizes[2],
-                                        w - corner_sizes[3][0] - corner_sizes[2][0], edge_sizes[2],
-                                        color);
-  if (edge_sizes[3] > 0) /* Left */
-    gsk_ngl_render_job_draw_with_color (job,
-                                        x, y + corner_sizes[0][1],
-                                        edge_sizes[3], h - corner_sizes[0][1] - corner_sizes[3][1],
-                                        color);
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_blurred_outset_shadow_node (GskNglRenderJob     *job,
-                                                     const GskRenderNode *node)
-{
-  const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
-  float scale_x = job->scale_x;
-  float scale_y = job->scale_y;
-  float blur_radius = gsk_outset_shadow_node_get_blur_radius (node);
-  float blur_extra = blur_radius * 2.0f; /* 2.0 = shader radius_multiplier */
-  float half_blur_extra = blur_extra / 2.0f;
-  int extra_blur_pixels_x = ceilf (half_blur_extra * scale_x);
-  int extra_blur_pixels_y = ceilf (half_blur_extra * scale_y);
-  float spread = gsk_outset_shadow_node_get_spread (node);
-  float dx = gsk_outset_shadow_node_get_dx (node);
-  float dy = gsk_outset_shadow_node_get_dy (node);
-  GskRoundedRect scaled_outline;
-  GskRoundedRect transformed_outline;
-  GskNglRenderOffscreen offscreen = {0};
-  int texture_width, texture_height;
-  int blurred_texture_id;
-  int cached_tid;
-  gboolean do_slicing;
-  guint16 color[4];
-  float half_width = outline->bounds.size.width / 2;
-  float half_height = outline->bounds.size.height / 2;
-
-  rgba_to_half (gsk_outset_shadow_node_get_color (node), color);
-
-  /* scaled_outline is the minimal outline we need to draw the given drop shadow,
-   * enlarged by the spread and offset by the blur radius. */
-  scaled_outline = *outline;
-
-  if (outline->bounds.size.width < blur_extra ||
-      outline->bounds.size.height < blur_extra ||
-      outline->corner[0].width >= half_width ||
-      outline->corner[1].width >= half_width ||
-      outline->corner[2].width >= half_width ||
-      outline->corner[3].width >= half_width ||
-      outline->corner[0].height >= half_height ||
-      outline->corner[1].height >= half_height ||
-      outline->corner[2].height >= half_height ||
-      outline->corner[3].height >= half_height)
-    {
-      do_slicing = FALSE;
-      gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread);
-    }
-  else
-    {
-      /* Shrink our outline to the minimum size that can still hold all the border radii */
-      gsk_rounded_rect_shrink_to_minimum (&scaled_outline);
-      /* Increase by the spread */
-      gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread);
-      /* Grow bounds but don't grow corners */
-      graphene_rect_inset (&scaled_outline.bounds, - blur_extra / 2.0, - blur_extra / 2.0);
-      /* For the center part, we add a few pixels */
-      scaled_outline.bounds.size.width += SHADOW_EXTRA_SIZE;
-      scaled_outline.bounds.size.height += SHADOW_EXTRA_SIZE;
-
-      do_slicing = TRUE;
-    }
-
-  texture_width = (int)ceil ((scaled_outline.bounds.size.width + blur_extra) * scale_x);
-  texture_height = (int)ceil ((scaled_outline.bounds.size.height + blur_extra) * scale_y);
-
-  scaled_outline.bounds.origin.x = extra_blur_pixels_x;
-  scaled_outline.bounds.origin.y = extra_blur_pixels_y;
-  scaled_outline.bounds.size.width = texture_width - (extra_blur_pixels_x * 2);
-  scaled_outline.bounds.size.height = texture_height - (extra_blur_pixels_y * 2);
-
-  for (guint i = 0; i < G_N_ELEMENTS (scaled_outline.corner); i++)
-    {
-      scaled_outline.corner[i].width *= scale_x;
-      scaled_outline.corner[i].height *= scale_y;
-    }
-
-  cached_tid = gsk_ngl_shadow_library_lookup (job->driver->shadows, &scaled_outline, blur_radius);
-
-  if (cached_tid == 0)
-    {
-      GdkGLContext *context = job->command_queue->context;
-      GskNglRenderTarget *render_target;
-      graphene_matrix_t prev_projection;
-      graphene_rect_t prev_viewport;
-      guint prev_fbo;
-
-      gsk_ngl_driver_create_render_target (job->driver,
-                                           texture_width, texture_height,
-                                           get_target_format (job, node),
-                                           GL_NEAREST, GL_NEAREST,
-                                           &render_target);
-
-      if (gdk_gl_context_has_debug (context))
-        {
-          gdk_gl_context_label_object_printf (context,
-                                              GL_TEXTURE,
-                                              render_target->texture_id,
-                                              "Outset Shadow Temp %d",
-                                              render_target->texture_id);
-          gdk_gl_context_label_object_printf  (context,
-                                               GL_FRAMEBUFFER,
-                                               render_target->framebuffer_id,
-                                               "Outset Shadow FB Temp %d",
-                                               render_target->framebuffer_id);
-        }
-
-      /* Change state for offscreen */
-      gsk_ngl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
-      gsk_ngl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
-      gsk_ngl_render_job_set_modelview (job, NULL);
-      gsk_ngl_render_job_push_clip (job, &scaled_outline);
-
-      /* Bind render target and clear it */
-      prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
-      gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-      /* Draw the outline using color program */
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
-      gsk_ngl_render_job_draw_with_color (job, 0, 0, texture_width, texture_height,
-                                          (guint16[]){ FP16_ONE, FP16_ONE, FP16_ONE, FP16_ONE });
-      gsk_ngl_render_job_end_draw (job);
-
-      /* Reset state from offscreen */
-      gsk_ngl_render_job_pop_clip (job);
-      gsk_ngl_render_job_pop_modelview (job);
-      gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
-      gsk_ngl_render_job_set_projection (job, &prev_projection);
-
-      /* Now blur the outline */
-      init_full_texture_region (&offscreen);
-      offscreen.texture_id = gsk_ngl_driver_release_render_target (job->driver, render_target, FALSE);
-      blurred_texture_id = blur_offscreen (job,
-                                           &offscreen,
-                                           texture_width,
-                                           texture_height,
-                                           blur_radius * scale_x,
-                                           blur_radius * scale_y);
-
-      gsk_ngl_shadow_library_insert (job->driver->shadows,
-                                     &scaled_outline,
-                                     blur_radius,
-                                     blurred_texture_id);
-
-      gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
-    }
-  else
-    {
-      blurred_texture_id = cached_tid;
-    }
-
-  gsk_ngl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
-
-  if (!do_slicing)
-    {
-      float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx);
-      float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy);
-
-      offscreen.was_offscreen = TRUE;
-      offscreen.texture_id = blurred_texture_id;
-      init_full_texture_region (&offscreen);
-
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           blurred_texture_id);
-      gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                                UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0,
-                                                &transformed_outline);
-      gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                    &GRAPHENE_RECT_INIT (min_x,
-                                                                         min_y,
-                                                                         texture_width / scale_x,
-                                                                         texture_height / scale_y),
-                                                    &offscreen,
-                                                    color);
-      gsk_ngl_render_job_end_draw (job);
-
-      return;
-    }
-
-  /* slicing */
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       blurred_texture_id);
-  gsk_ngl_program_set_uniform_rounded_rect (job->current_program,
-                                            UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0,
-                                            &transformed_outline);
-
-  {
-    float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx);
-    float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy);
-    float max_x = ceilf (outline->bounds.origin.x + outline->bounds.size.width +
-                         half_blur_extra + dx + spread);
-    float max_y = ceilf (outline->bounds.origin.y + outline->bounds.size.height +
-                         half_blur_extra + dy + spread);
-    const GskNglTextureNineSlice *slices;
-    float left_width, center_width, right_width;
-    float top_height, center_height, bottom_height;
-    GskNglTexture *texture;
-
-    texture = gsk_ngl_driver_get_texture_by_id (job->driver, blurred_texture_id);
-    slices = gsk_ngl_texture_get_nine_slice (texture, &scaled_outline, extra_blur_pixels_x, extra_blur_pixels_y);
-
-    offscreen.was_offscreen = TRUE;
-
-    /* Our texture coordinates MUST be scaled, while the actual vertex coords
-     * MUST NOT be scaled.
-     */
-
-    left_width = slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x;
-    right_width = slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x;
-    center_width = (max_x - min_x) - (left_width + right_width);
-
-    top_height = slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y;
-    bottom_height = slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y;
-    center_height = (max_y - min_y) - (top_height + bottom_height);
-
-    /* Top left */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_LEFT]))
-      {
-        memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_LEFT].area, sizeof offscreen.area);
-        gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                      &GRAPHENE_RECT_INIT (min_x,
-                                                                           min_y,
-                                                                           left_width,
-                                                                           top_height),
-                                                      &offscreen,
-                                                      color);
-      }
-
-    /* Top center */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_CENTER]))
-    {
-      memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_CENTER].area, sizeof offscreen.area);
-      gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                    &GRAPHENE_RECT_INIT (min_x + left_width,
-                                                                         min_y,
-                                                                         center_width,
-                                                                         top_height),
-                                                    &offscreen,
-                                                    color);
-    }
-
-    /* Top right */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_RIGHT]))
-    {
-      memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_RIGHT].area, sizeof offscreen.area);
-      gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                    &GRAPHENE_RECT_INIT (max_x - right_width,
-                                                                         min_y,
-                                                                         right_width,
-                                                                         top_height),
-                                                    &offscreen,
-                                                    color);
-    }
-
-    /* Bottom right */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_RIGHT]))
-    {
-      memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_RIGHT].area, sizeof offscreen.area);
-      gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                    &GRAPHENE_RECT_INIT (max_x - right_width,
-                                                                         max_y - bottom_height,
-                                                                         right_width,
-                                                                         bottom_height),
-                                                    &offscreen,
-                                                    color);
-    }
-
-    /* Bottom left */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_LEFT]))
-      {
-        memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_LEFT].area, sizeof offscreen.area);
-        gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                      &GRAPHENE_RECT_INIT (min_x,
-                                                                           max_y - bottom_height,
-                                                                           left_width,
-                                                                           bottom_height),
-                                                      &offscreen,
-                                                      color);
-      }
-
-    /* Left side */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_LEFT_CENTER]))
-      {
-        memcpy (&offscreen.area, &slices[NINE_SLICE_LEFT_CENTER].area, sizeof offscreen.area);
-        gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                      &GRAPHENE_RECT_INIT (min_x,
-                                                                           min_y + top_height,
-                                                                           left_width,
-                                                                           center_height),
-                                                      &offscreen,
-                                                      color);
-      }
-
-    /* Right side */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_RIGHT_CENTER]))
-      {
-        memcpy (&offscreen.area, &slices[NINE_SLICE_RIGHT_CENTER].area, sizeof offscreen.area);
-        gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                      &GRAPHENE_RECT_INIT (max_x - right_width,
-                                                                           min_y + top_height,
-                                                                           right_width,
-                                                                           center_height),
-                                                      &offscreen,
-                                                      color);
-      }
-
-    /* Bottom side */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_CENTER]))
-      {
-        memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_CENTER].area, sizeof offscreen.area);
-        gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                      &GRAPHENE_RECT_INIT (min_x + left_width,
-                                                                           max_y - bottom_height,
-                                                                           center_width,
-                                                                           bottom_height),
-                                                      &offscreen,
-                                                      color);
-      }
-
-    /* Middle */
-    if (nine_slice_is_visible (&slices[NINE_SLICE_CENTER]))
-      {
-        if (!gsk_rounded_rect_contains_rect (outline, &GRAPHENE_RECT_INIT (min_x + left_width,
-                                                                           min_y + top_height,
-                                                                           center_width,
-                                                                           center_height)))
-          {
-            memcpy (&offscreen.area, &slices[NINE_SLICE_CENTER].area, sizeof offscreen.area);
-            gsk_ngl_render_job_draw_offscreen_with_color (job,
-                                                          &GRAPHENE_RECT_INIT (min_x + left_width,
-                                                                               min_y + top_height,
-                                                                               center_width,
-                                                                               center_height),
-                                                          &offscreen,
-                                                          color);
-          }
-      }
-  }
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline gboolean G_GNUC_PURE
-equal_texture_nodes (const GskRenderNode *node1,
-                     const GskRenderNode *node2)
-{
-  if (gsk_render_node_get_node_type (node1) != GSK_TEXTURE_NODE ||
-      gsk_render_node_get_node_type (node2) != GSK_TEXTURE_NODE)
-    return FALSE;
-
-  if (gsk_texture_node_get_texture (node1) !=
-      gsk_texture_node_get_texture (node2))
-    return FALSE;
-
-  return graphene_rect_equal (&node1->bounds, &node2->bounds);
-}
-
-static inline void
-gsk_ngl_render_job_visit_cross_fade_node (GskNglRenderJob     *job,
-                                          const GskRenderNode *node)
-{
-  const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
-  const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
-  float progress = gsk_cross_fade_node_get_progress (node);
-  GskNglRenderOffscreen offscreen_start = {0};
-  GskNglRenderOffscreen offscreen_end = {0};
-
-  g_assert (progress > 0.0);
-  g_assert (progress < 1.0);
-
-  offscreen_start.force_offscreen = TRUE;
-  offscreen_start.reset_clip = TRUE;
-  offscreen_start.bounds = &node->bounds;
-
-  offscreen_end.force_offscreen = TRUE;
-  offscreen_end.reset_clip = TRUE;
-  offscreen_end.bounds = &node->bounds;
-
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start))
-    {
-      gsk_ngl_render_job_visit_node (job, end_node);
-      return;
-    }
-
-  g_assert (offscreen_start.texture_id);
-
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, end_node, &offscreen_end))
-    {
-      float prev_alpha = gsk_ngl_render_job_set_alpha (job, job->alpha * progress);
-      gsk_ngl_render_job_visit_node (job, start_node);
-      gsk_ngl_render_job_set_alpha (job, prev_alpha);
-      return;
-    }
-
-  g_assert (offscreen_end.texture_id);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, cross_fade));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       offscreen_start.texture_id);
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_CROSS_FADE_SOURCE2, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE1,
-                                       offscreen_end.texture_id);
-  gsk_ngl_program_set_uniform1f (job->current_program,
-                                 UNIFORM_CROSS_FADE_PROGRESS, 0,
-                                 progress);
-  gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen_end);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static gboolean
-is_non_branching (const GskRenderNode *node)
-{
-  switch ((int)gsk_render_node_get_node_type (node))
-    {
-    case GSK_COLOR_NODE:
-    case GSK_LINEAR_GRADIENT_NODE:
-    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
-    case GSK_RADIAL_GRADIENT_NODE:
-    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
-    case GSK_CONIC_GRADIENT_NODE:
-    case GSK_BORDER_NODE:
-    case GSK_TEXTURE_NODE:
-    case GSK_INSET_SHADOW_NODE:
-    case GSK_OUTSET_SHADOW_NODE:
-    case GSK_TEXT_NODE:
-    case GSK_CAIRO_NODE:
-      return TRUE;
-
-    case GSK_TRANSFORM_NODE:
-      return is_non_branching (gsk_transform_node_get_child (node));
-
-    case GSK_OPACITY_NODE:
-      return is_non_branching (gsk_opacity_node_get_child (node));
-
-    case GSK_COLOR_MATRIX_NODE:
-      return is_non_branching (gsk_color_matrix_node_get_child (node));
-
-    case GSK_CLIP_NODE:
-      return is_non_branching (gsk_clip_node_get_child (node));
-
-    case GSK_ROUNDED_CLIP_NODE:
-      return is_non_branching (gsk_rounded_clip_node_get_child (node));
-
-    case GSK_SHADOW_NODE:
-      return is_non_branching (gsk_shadow_node_get_child (node));
-
-    case GSK_BLUR_NODE:
-      return is_non_branching (gsk_shadow_node_get_child (node));
-
-    case GSK_DEBUG_NODE:
-      return is_non_branching (gsk_debug_node_get_child (node));
-
-    case GSK_CONTAINER_NODE:
-      return gsk_container_node_get_n_children (node) == 1 &&
-             is_non_branching (gsk_container_node_get_child (node, 0));
-
-    default:
-      return FALSE;
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_opacity_node (GskNglRenderJob     *job,
-                                       const GskRenderNode *node)
-{
-  const GskRenderNode *child = gsk_opacity_node_get_child (node);
-  float opacity = gsk_opacity_node_get_opacity (node);
-  float new_alpha = job->alpha * opacity;
-
-  if (!ALPHA_IS_CLEAR (new_alpha))
-    {
-      float prev_alpha = gsk_ngl_render_job_set_alpha (job, new_alpha);
-
-      /* Handle a few easy cases without offscreen. We bail out
-       * as soon as we see nodes with multiple children - in theory,
-       * we would only need offscreens for overlapping children.
-       */
-      if (is_non_branching (child))
-        {
-          gsk_ngl_render_job_visit_node (job, child);
-          gsk_ngl_render_job_set_alpha (job, prev_alpha);
-        }
-      else
-        {
-          GskNglRenderOffscreen offscreen = {0};
-
-          offscreen.bounds = &child->bounds;
-          offscreen.force_offscreen = TRUE;
-          offscreen.reset_clip = TRUE;
-
-          /* Note: offscreen rendering resets alpha to 1.0 */
-          if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
-            return;
-
-          g_assert (offscreen.texture_id);
-
-          gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-          gsk_ngl_program_set_uniform_texture (job->current_program,
-                                               UNIFORM_SHARED_SOURCE, 0,
-                                               GL_TEXTURE_2D,
-                                               GL_TEXTURE0,
-                                               offscreen.texture_id);
-          gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-          gsk_ngl_render_job_end_draw (job);
-        }
-
-      gsk_ngl_render_job_set_alpha (job, prev_alpha);
-    }
-}
-
-static inline int
-compute_phase_and_pos (float value, float *pos)
-{
-  float v;
-
-  *pos = floorf (value);
-
-  v = value - *pos;
-
-  if (v < 0.125)
-    return 0;
-  else if (v < 0.375)
-    return 1;
-  else if (v < 0.625)
-    return 2;
-  else if (v < 0.875)
-    return 3;
-  else
-    {
-      *pos += 1;
-      return 0;
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_text_node (GskNglRenderJob     *job,
-                                    const GskRenderNode *node,
-                                    const GdkRGBA       *color,
-                                    gboolean             force_color)
-{
-  const PangoFont *font = gsk_text_node_get_font (node);
-  const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL);
-  const graphene_point_t *offset = gsk_text_node_get_offset (node);
-  float text_scale = MAX (job->scale_x, job->scale_y); /* TODO: Fix for uneven scales? */
-  guint num_glyphs = gsk_text_node_get_num_glyphs (node);
-  float x = offset->x + job->offset_x;
-  float y = offset->y + job->offset_y;
-  GskNglGlyphLibrary *library = job->driver->glyphs;
-  GskNglCommandBatch *batch;
-  int x_position = 0;
-  GskNglGlyphKey lookup;
-  guint last_texture = 0;
-  GskNglDrawVertex *vertices;
-  guint used = 0;
-  guint16 nc[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE };
-  guint16 cc[4];
-  const guint16 *c;
-  const PangoGlyphInfo *gi;
-  guint i;
-  int yshift;
-  float ypos;
-
-  if (num_glyphs == 0)
-    return;
-
-  if ((force_color || !gsk_text_node_has_color_glyphs (node)) &&
-      RGBA_IS_CLEAR (color))
-    return;
-
-  rgba_to_half (color, cc);
-
-  lookup.font = (PangoFont *)font;
-  lookup.scale = (guint) (text_scale * 1024);
-
-  yshift = compute_phase_and_pos (y, &ypos);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
-
-  batch = gsk_ngl_command_queue_get_batch (job->command_queue);
-  vertices = gsk_ngl_command_queue_add_n_vertices (job->command_queue, num_glyphs);
-
-  /* We use one quad per character */
-  for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++)
-    {
-      const GskNglGlyphValue *glyph;
-      float glyph_x, glyph_y, glyph_x2, glyph_y2;
-      float tx, ty, tx2, ty2;
-      float cx;
-      float cy;
-      guint texture_id;
-
-      lookup.glyph = gi->glyph;
-
-      /* If the glyph has color, we don't need to recolor anything.
-       * We tell the shader by setting the color to vec4(-1).
-       */
-      if (!force_color && gi->attr.is_color)
-        c = nc;
-      else
-        c = cc;
-
-      cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
-      lookup.xshift = compute_phase_and_pos (x + cx, &cx);
-
-      if G_UNLIKELY (gi->geometry.y_offset != 0)
-        {
-          cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
-          lookup.yshift = compute_phase_and_pos (y + cy, &cy);
-        }
-      else
-        {
-          lookup.yshift = yshift;
-          cy = ypos;
-        }
-
-      x_position += gi->geometry.width;
-
-      texture_id = gsk_ngl_glyph_library_lookup_or_add (library, &lookup, &glyph);
-      if G_UNLIKELY (texture_id == 0)
-        continue;
-
-      if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_NGL_N_VERTICES > 0xffff)
-        {
-          if G_LIKELY (last_texture != 0)
-            {
-              guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count;
-
-              /* Since we have batched added our VBO vertices to avoid repeated
-               * calls to the buffer, we need to manually tweak the vbo offset
-               * of the new batch as otherwise it will point at the end of our
-               * vbo array.
-               */
-              gsk_ngl_render_job_split_draw (job);
-              batch = gsk_ngl_command_queue_get_batch (job->command_queue);
-              batch->draw.vbo_offset = vbo_offset;
-            }
-
-          gsk_ngl_program_set_uniform_texture (job->current_program,
-                                               UNIFORM_SHARED_SOURCE, 0,
-                                               GL_TEXTURE_2D,
-                                               GL_TEXTURE0,
-                                               texture_id);
-          last_texture = texture_id;
-        }
-
-      tx = glyph->entry.area.x;
-      ty = glyph->entry.area.y;
-      tx2 = glyph->entry.area.x2;
-      ty2 = glyph->entry.area.y2;
-
-      glyph_x = cx + glyph->ink_rect.x;
-      glyph_y = cy + glyph->ink_rect.y;
-      glyph_x2 = glyph_x + glyph->ink_rect.width;
-      glyph_y2 = glyph_y + glyph->ink_rect.height;
-
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x,  glyph_y  }, .uv = { tx,  ty  }, .color = { c[0], c[1], c[2], c[3] } };
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x,  glyph_y2 }, .uv = { tx,  ty2 }, .color = { c[0], c[1], c[2], c[3] } };
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x2, glyph_y  }, .uv = { tx2, ty  }, .color = { c[0], c[1], c[2], c[3] } };
-
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x2, glyph_y2 }, .uv = { tx2, ty2 }, .color = { c[0], c[1], c[2], c[3] } };
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x,  glyph_y2 }, .uv = { tx,  ty2 }, .color = { c[0], c[1], c[2], c[3] } };
-      *(vertices++) = (GskNglDrawVertex) { .position = { glyph_x2, glyph_y  }, .uv = { tx2, ty  }, .color = { c[0], c[1], c[2], c[3] } };
-
-      batch->draw.vbo_count += GSK_NGL_N_VERTICES;
-      used++;
-    }
-
-  if (used != num_glyphs)
-    gsk_ngl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used);
-
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_shadow_node (GskNglRenderJob     *job,
-                                      const GskRenderNode *node)
-{
-  const gsize n_shadows = gsk_shadow_node_get_n_shadows (node);
-  const GskRenderNode *original_child = gsk_shadow_node_get_child (node);
-  const GskRenderNode *shadow_child = original_child;
-
-  /* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact.
-   * If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */
-  if (gsk_render_node_get_node_type (shadow_child) == GSK_COLOR_MATRIX_NODE &&
-      !color_matrix_modifies_alpha (shadow_child))
-    shadow_child = gsk_color_matrix_node_get_child (shadow_child);
-
-  for (guint i = 0; i < n_shadows; i++)
-    {
-      const GskShadow *shadow = gsk_shadow_node_get_shadow (node, i);
-      const float dx = shadow->dx;
-      const float dy = shadow->dy;
-      GskNglRenderOffscreen offscreen = {0};
-      graphene_rect_t bounds;
-      guint16 color[4];
-
-      if (RGBA_IS_CLEAR (&shadow->color))
-        continue;
-
-      if (node_is_invisible (shadow_child))
-        continue;
-
-      if (shadow->radius == 0 &&
-          gsk_render_node_get_node_type (shadow_child) == GSK_TEXT_NODE)
-        {
-          if (dx != 0 || dy != 0)
-            {
-              gsk_ngl_render_job_offset (job, dx, dy);
-              gsk_ngl_render_job_visit_text_node (job, shadow_child, &shadow->color, TRUE);
-              gsk_ngl_render_job_offset (job, -dx, -dy);
-            }
-          continue;
-        }
-
-      if (shadow->radius > 0)
-        {
-          float min_x;
-          float min_y;
-          float max_x;
-          float max_y;
-
-          offscreen.do_not_cache = TRUE;
-
-          blur_node (job,
-                     &offscreen,
-                     shadow_child,
-                     shadow->radius,
-                     &min_x, &max_x,
-                     &min_y, &max_y);
-
-          bounds.origin.x = min_x - job->offset_x;
-          bounds.origin.y = min_y - job->offset_y;
-          bounds.size.width = max_x - min_x;
-          bounds.size.height = max_y - min_y;
-
-          offscreen.was_offscreen = TRUE;
-        }
-      else if (dx == 0 && dy == 0)
-        {
-          continue; /* Invisible anyway */
-        }
-      else
-        {
-          offscreen.bounds = &shadow_child->bounds;
-          offscreen.reset_clip = TRUE;
-          offscreen.do_not_cache = TRUE;
-
-          if (!gsk_ngl_render_job_visit_node_with_offscreen (job, shadow_child, &offscreen))
-            g_assert_not_reached ();
-
-          bounds = shadow_child->bounds;
-        }
-
-      gsk_ngl_render_job_offset (job, dx, dy);
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           offscreen.texture_id);
-      rgba_to_half (&shadow->color, color);
-      gsk_ngl_render_job_draw_offscreen_with_color (job, &bounds, &offscreen, color);
-      gsk_ngl_render_job_end_draw (job);
-      gsk_ngl_render_job_offset (job, -dx, -dy);
-    }
-
-  /* Now draw the child normally */
-  gsk_ngl_render_job_visit_node (job, original_child);
-}
-
-static inline void
-gsk_ngl_render_job_visit_blur_node (GskNglRenderJob     *job,
-                                    const GskRenderNode *node)
-{
-  const GskRenderNode *child = gsk_blur_node_get_child (node);
-  float blur_radius = gsk_blur_node_get_radius (node);
-  GskNglRenderOffscreen offscreen = {0};
-  GskTextureKey key;
-  gboolean cache_texture;
-  float min_x;
-  float max_x;
-  float min_y;
-  float max_y;
-
-  g_assert (blur_radius > 0);
-
-  if (node_is_invisible (child))
-    return;
-
-  key.pointer = node;
-  key.pointer_is_child = FALSE;
-  key.scale_x = job->scale_x;
-  key.scale_y = job->scale_y;
-  key.filter = GL_NEAREST;
-
-  offscreen.texture_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
-  cache_texture = offscreen.texture_id == 0;
-
-  blur_node (job,
-             &offscreen,
-             child,
-             blur_radius,
-             &min_x, &max_x, &min_y, &max_y);
-
-  g_assert (offscreen.texture_id != 0);
-
-  if (cache_texture)
-    gsk_ngl_driver_cache_texture (job->driver, &key, offscreen.texture_id);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       offscreen.texture_id);
-  gsk_ngl_render_job_draw_coords (job,
-                                  min_x, min_y, max_x, max_y,
-                                  0, 1, 1, 0,
-                                  (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } );
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_blend_node (GskNglRenderJob     *job,
-                                     const GskRenderNode *node)
-{
-  const GskRenderNode *top_child = gsk_blend_node_get_top_child (node);
-  const GskRenderNode *bottom_child = gsk_blend_node_get_bottom_child (node);
-  GskNglRenderOffscreen top_offscreen = {0};
-  GskNglRenderOffscreen bottom_offscreen = {0};
-
-  top_offscreen.bounds = &node->bounds;
-  top_offscreen.force_offscreen = TRUE;
-  top_offscreen.reset_clip = TRUE;
-
-  bottom_offscreen.bounds = &node->bounds;
-  bottom_offscreen.force_offscreen = TRUE;
-  bottom_offscreen.reset_clip = TRUE;
-
-  /* TODO: We create 2 textures here as big as the blend node, but both the
-   * start and the end node might be a lot smaller than that. */
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, bottom_child, &bottom_offscreen))
-    {
-      gsk_ngl_render_job_visit_node (job, top_child);
-      return;
-    }
-
-  g_assert (bottom_offscreen.was_offscreen);
-
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, top_child, &top_offscreen))
-    {
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           bottom_offscreen.texture_id);
-      gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &bottom_offscreen);
-      gsk_ngl_render_job_end_draw (job);
-      return;
-    }
-
-  g_assert (top_offscreen.was_offscreen);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blend));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       bottom_offscreen.texture_id);
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_BLEND_SOURCE2, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE1,
-                                       top_offscreen.texture_id);
-  gsk_ngl_program_set_uniform1i (job->current_program,
-                                 UNIFORM_BLEND_MODE, 0,
-                                 gsk_blend_node_get_blend_mode (node));
-  gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_color_matrix_node (GskNglRenderJob     *job,
-                                            const GskRenderNode *node)
-{
-  const GskRenderNode *child = gsk_color_matrix_node_get_child (node);
-  GskNglRenderOffscreen offscreen = {0};
-  float offset[4];
-
-  if (node_is_invisible (child))
-    return;
-
-  offscreen.bounds = &node->bounds;
-  offscreen.reset_clip = TRUE;
-
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
-    g_assert_not_reached ();
-
-  g_assert (offscreen.texture_id > 0);
-
-  graphene_vec4_to_float (gsk_color_matrix_node_get_color_offset (node), offset);
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color_matrix));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       offscreen.texture_id);
-  gsk_ngl_program_set_uniform_matrix (job->current_program,
-                                      UNIFORM_COLOR_MATRIX_COLOR_MATRIX, 0,
-                                      gsk_color_matrix_node_get_color_matrix (node));
-  gsk_ngl_program_set_uniform4fv (job->current_program,
-                                  UNIFORM_COLOR_MATRIX_COLOR_OFFSET, 0,
-                                  1,
-                                  offset);
-  gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_gl_shader_node_fallback (GskNglRenderJob     *job,
-                                                  const GskRenderNode *node)
-{
-  guint16 pink[4] = { 15360, 13975, 14758, 15360 }; /* 255 105 180 */
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color));
-  gsk_ngl_render_job_draw_rect_with_color (job, &node->bounds, pink);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static inline void
-gsk_ngl_render_job_visit_gl_shader_node (GskNglRenderJob     *job,
-                                         const GskRenderNode *node)
-{
-  GError *error = NULL;
-  GskGLShader *shader;
-  GskNglProgram *program;
-  int n_children;
-
-  shader = gsk_gl_shader_node_get_shader (node);
-  program = gsk_ngl_driver_lookup_shader (job->driver, shader, &error);
-  n_children = gsk_gl_shader_node_get_n_children (node);
-
-  if G_UNLIKELY (program == NULL)
-    {
-      if (g_object_get_data (G_OBJECT (shader), "gsk-did-warn") == NULL)
-        {
-          g_object_set_data (G_OBJECT (shader), "gsk-did-warn", GUINT_TO_POINTER (1));
-          g_warning ("Failed to compile gl shader: %s", error->message);
-        }
-      gsk_ngl_render_job_visit_gl_shader_node_fallback (job, node);
-      g_clear_error (&error);
-    }
-  else
-    {
-      GskNglRenderOffscreen offscreens[4] = {{0}};
-      const GskGLUniform *uniforms;
-      const guint8 *base;
-      GBytes *args;
-      int n_uniforms;
-
-      g_assert (n_children < G_N_ELEMENTS (offscreens));
-
-      for (guint i = 0; i < n_children; i++)
-        {
-          const GskRenderNode *child = gsk_gl_shader_node_get_child (node, i);
-
-          offscreens[i].bounds = &node->bounds;
-          offscreens[i].force_offscreen = TRUE;
-          offscreens[i].reset_clip = TRUE;
-
-          if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreens[i]))
-            return;
-        }
-
-      args = gsk_gl_shader_node_get_args (node);
-      base = g_bytes_get_data (args, NULL);
-      uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
-
-      gsk_ngl_render_job_begin_draw (job, program);
-      for (guint i = 0; i < n_children; i++)
-        gsk_ngl_program_set_uniform_texture (program,
-                                             UNIFORM_CUSTOM_TEXTURE1 + i, 0,
-                                             GL_TEXTURE_2D,
-                                             GL_TEXTURE0 + i,
-                                             offscreens[i].texture_id);
-      gsk_ngl_program_set_uniform2f (program,
-                                     UNIFORM_CUSTOM_SIZE, 0,
-                                     node->bounds.size.width,
-                                     node->bounds.size.height);
-      for (guint i = 0; i < n_uniforms; i++)
-        {
-          const GskGLUniform *u = &uniforms[i];
-          const guint8 *data = base + u->offset;
-
-          switch (u->type)
-            {
-            default:
-            case GSK_GL_UNIFORM_TYPE_NONE:
-              break;
-            case GSK_GL_UNIFORM_TYPE_FLOAT:
-              gsk_ngl_uniform_state_set1fv (job->command_queue->uniforms,
-                                            program->program_info,
-                                            UNIFORM_CUSTOM_ARG0 + i,
-                                            0, 1, (const float *)data);
-              break;
-            case GSK_GL_UNIFORM_TYPE_INT:
-              gsk_ngl_uniform_state_set1i (job->command_queue->uniforms,
-                                           program->program_info,
-                                           UNIFORM_CUSTOM_ARG0 + i,
-                                           0, *(const gint32 *)data);
-              break;
-            case GSK_GL_UNIFORM_TYPE_UINT:
-            case GSK_GL_UNIFORM_TYPE_BOOL:
-              gsk_ngl_uniform_state_set1ui (job->command_queue->uniforms,
-                                            program->program_info,
-                                            UNIFORM_CUSTOM_ARG0 + i,
-                                            0, *(const guint32 *)data);
-              break;
-            case GSK_GL_UNIFORM_TYPE_VEC2:
-              gsk_ngl_uniform_state_set2fv (job->command_queue->uniforms,
-                                            program->program_info,
-                                            UNIFORM_CUSTOM_ARG0 + i,
-                                            0, 1, (const float *)data);
-              break;
-            case GSK_GL_UNIFORM_TYPE_VEC3:
-              gsk_ngl_uniform_state_set3fv (job->command_queue->uniforms,
-                                            program->program_info,
-                                            UNIFORM_CUSTOM_ARG0 + i,
-                                            0, 1, (const float *)data);
-              break;
-            case GSK_GL_UNIFORM_TYPE_VEC4:
-              gsk_ngl_uniform_state_set4fv (job->command_queue->uniforms,
-                                            program->program_info,
-                                            UNIFORM_CUSTOM_ARG0 + i,
-                                            0, 1, (const float *)data);
-              break;
-            }
-        }
-      gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
-      gsk_ngl_render_job_end_draw (job);
-    }
-}
-
-static void
-gsk_ngl_render_job_upload_texture (GskNglRenderJob       *job,
-                                   GdkTexture            *texture,
-                                   GskNglRenderOffscreen *offscreen)
-{
-  if (gsk_ngl_texture_library_can_cache ((GskNglTextureLibrary *)job->driver->icons,
-                                        texture->width,
-                                        texture->height) &&
-      !GDK_IS_GL_TEXTURE (texture))
-    {
-      const GskNglIconData *icon_data;
-
-      gsk_ngl_icon_library_lookup_or_add (job->driver->icons, texture, &icon_data);
-      offscreen->texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
-      memcpy (&offscreen->area, &icon_data->entry.area, sizeof offscreen->area);
-    }
-  else
-    {
-      offscreen->texture_id = gsk_ngl_driver_load_texture (job->driver, texture, GL_LINEAR, GL_LINEAR);
-      init_full_texture_region (offscreen);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_texture_node (GskNglRenderJob     *job,
-                                       const GskRenderNode *node)
-{
-  GdkTexture *texture = gsk_texture_node_get_texture (node);
-  int max_texture_size = job->command_queue->max_texture_size;
-
-  if G_LIKELY (texture->width <= max_texture_size &&
-               texture->height <= max_texture_size)
-    {
-      GskNglRenderOffscreen offscreen = {0};
-
-      gsk_ngl_render_job_upload_texture (job, texture, &offscreen);
-
-      g_assert (offscreen.texture_id);
-      g_assert (offscreen.was_offscreen == FALSE);
-
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-      gsk_ngl_program_set_uniform_texture (job->current_program,
-                                           UNIFORM_SHARED_SOURCE, 0,
-                                           GL_TEXTURE_2D,
-                                           GL_TEXTURE0,
-                                           offscreen.texture_id);
-      gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-      gsk_ngl_render_job_end_draw (job);
-    }
-  else
-    {
-      float min_x = job->offset_x + node->bounds.origin.x;
-      float min_y = job->offset_y + node->bounds.origin.y;
-      float max_x = min_x + node->bounds.size.width;
-      float max_y = min_y + node->bounds.size.height;
-      float scale_x = (max_x - min_x) / texture->width;
-      float scale_y = (max_y - min_y) / texture->height;
-      GskNglTextureSlice *slices = NULL;
-      guint n_slices = 0;
-
-      gsk_ngl_driver_slice_texture (job->driver, texture, &slices, &n_slices);
-
-      g_assert (slices != NULL);
-      g_assert (n_slices > 0);
-
-      gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-
-      for (guint i = 0; i < n_slices; i ++)
-        {
-          const GskNglTextureSlice *slice = &slices[i];
-          float x1, x2, y1, y2;
-
-          x1 = min_x + (scale_x * slice->rect.x);
-          x2 = x1 + (slice->rect.width * scale_x);
-          y1 = min_y + (scale_y * slice->rect.y);
-          y2 = y1 + (slice->rect.height * scale_y);
-
-          if (i > 0)
-            gsk_ngl_render_job_split_draw (job);
-          gsk_ngl_program_set_uniform_texture (job->current_program,
-                                               UNIFORM_SHARED_SOURCE, 0,
-                                               GL_TEXTURE_2D,
-                                               GL_TEXTURE0,
-                                               slice->texture_id);
-
-          gsk_ngl_render_job_draw_coords (job,
-                                          x1, y1, x2, y2,
-                                          0, 0, 1, 1,
-                                          (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO });
-        }
-
-      gsk_ngl_render_job_end_draw (job);
-    }
-}
-
-static inline void
-gsk_ngl_render_job_visit_repeat_node (GskNglRenderJob     *job,
-                                      const GskRenderNode *node)
-{
-  const GskRenderNode *child = gsk_repeat_node_get_child (node);
-  const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node);
-  GskNglRenderOffscreen offscreen = {0};
-
-  if (node_is_invisible (child))
-    return;
-
-  if (!graphene_rect_equal (child_bounds, &child->bounds))
-    {
-      /* TODO: implement these repeat nodes. */
-      gsk_ngl_render_job_visit_as_fallback (job, node);
-      return;
-    }
-
-  /* If the size of the repeat node is smaller than the size of the
-   * child node, we don't repeat at all and can just draw that part
-   * of the child texture... */
-  if (rect_contains_rect (child_bounds, &node->bounds))
-    {
-      gsk_ngl_render_job_visit_clipped_child (job, child, &node->bounds);
-      return;
-    }
-
-  offscreen.bounds = &child->bounds;
-  offscreen.reset_clip = TRUE;
-
-  if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
-    g_assert_not_reached ();
-
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, repeat));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       offscreen.texture_id);
-  gsk_ngl_program_set_uniform4f (job->current_program,
-                                 UNIFORM_REPEAT_CHILD_BOUNDS, 0,
-                                 (node->bounds.origin.x - child_bounds->origin.x) / child_bounds->size.width,
-                                 (node->bounds.origin.y - child_bounds->origin.y) / child_bounds->size.height,
-                                 node->bounds.size.width / child_bounds->size.width,
-                                 node->bounds.size.height / child_bounds->size.height);
-  gsk_ngl_program_set_uniform4f (job->current_program,
-                                 UNIFORM_REPEAT_TEXTURE_RECT, 0,
-                                 offscreen.area.x,
-                                 offscreen.was_offscreen ? offscreen.area.y2 : offscreen.area.y,
-                                 offscreen.area.x2,
-                                 offscreen.was_offscreen ? offscreen.area.y : offscreen.area.y2);
-  gsk_ngl_render_job_draw_offscreen (job, &node->bounds, &offscreen);
-  gsk_ngl_render_job_end_draw (job);
-}
-
-static void
-gsk_ngl_render_job_visit_node (GskNglRenderJob     *job,
-                               const GskRenderNode *node)
-{
-  gboolean has_clip;
-
-  g_assert (job != NULL);
-  g_assert (node != NULL);
-  g_assert (GSK_IS_NGL_DRIVER (job->driver));
-  g_assert (GSK_IS_NGL_COMMAND_QUEUE (job->command_queue));
-
-  if (node_is_invisible (node))
-    return;
-
-  if (!gsk_ngl_render_job_update_clip (job, &node->bounds, &has_clip))
-    return;
-
-  switch (gsk_render_node_get_node_type (node))
-    {
-    case GSK_BLEND_NODE:
-      gsk_ngl_render_job_visit_blend_node (job, node);
-    break;
-
-    case GSK_BLUR_NODE:
-      if (gsk_blur_node_get_radius (node) > 0)
-        gsk_ngl_render_job_visit_blur_node (job, node);
-      else
-        gsk_ngl_render_job_visit_node (job, gsk_blur_node_get_child (node));
-    break;
-
-    case GSK_BORDER_NODE:
-      if (gsk_border_node_get_uniform_color (node) &&
-          gsk_rounded_rect_is_rectilinear (gsk_border_node_get_outline (node)))
-        gsk_ngl_render_job_visit_rect_border_node (job, node);
-      else
-        gsk_ngl_render_job_visit_border_node (job, node);
-    break;
-
-    case GSK_CLIP_NODE:
-      gsk_ngl_render_job_visit_clip_node (job, node);
-    break;
-
-    case GSK_COLOR_NODE:
-      gsk_ngl_render_job_visit_color_node (job, node);
-    break;
-
-    case GSK_COLOR_MATRIX_NODE:
-      gsk_ngl_render_job_visit_color_matrix_node (job, node);
-    break;
-
-    case GSK_CONIC_GRADIENT_NODE:
-      if (gsk_conic_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
-        gsk_ngl_render_job_visit_conic_gradient_node (job, node);
-      else
-        gsk_ngl_render_job_visit_as_fallback (job, node);
-    break;
-
-    case GSK_CONTAINER_NODE:
-      {
-        GskRenderNode **children;
-        guint n_children;
-
-        children = gsk_container_node_get_children (node, &n_children);
-
-        for (guint i = 0; i < n_children; i++)
-          {
-            const GskRenderNode *child = children[i];
-
-            if (i + 1 < n_children &&
-                job->current_clip->is_fully_contained &&
-                gsk_render_node_get_node_type (child) == GSK_ROUNDED_CLIP_NODE)
-              {
-                const GskRenderNode *grandchild = gsk_rounded_clip_node_get_child (child);
-                const GskRenderNode *child2 = children[i + 1];
-                if (gsk_render_node_get_node_type (grandchild) == GSK_COLOR_NODE &&
-                    gsk_render_node_get_node_type (child2) == GSK_BORDER_NODE &&
-                    gsk_border_node_get_uniform_color (child2) &&
-                    rounded_rect_equal (gsk_rounded_clip_node_get_clip (child),
-                                        gsk_border_node_get_outline (child2)))
-                  {
-                    gsk_ngl_render_job_visit_css_background (job, child, child2);
-                    i++; /* skip the border node */
-                    continue;
-                  }
-              }
-
-            gsk_ngl_render_job_visit_node (job, child);
-          }
-      }
-    break;
-
-    case GSK_CROSS_FADE_NODE:
-      {
-        const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node);
-        const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node);
-        float progress = gsk_cross_fade_node_get_progress (node);
-
-        if (progress <= 0.0f)
-          gsk_ngl_render_job_visit_node (job, gsk_cross_fade_node_get_start_child (node));
-        else if (progress >= 1.0f || equal_texture_nodes (start_node, end_node))
-          gsk_ngl_render_job_visit_node (job, gsk_cross_fade_node_get_end_child (node));
-        else
-          gsk_ngl_render_job_visit_cross_fade_node (job, node);
-      }
-    break;
-
-    case GSK_DEBUG_NODE:
-      /* Debug nodes are ignored because draws get reordered anyway */
-      gsk_ngl_render_job_visit_node (job, gsk_debug_node_get_child (node));
-    break;
-
-    case GSK_GL_SHADER_NODE:
-      gsk_ngl_render_job_visit_gl_shader_node (job, node);
-    break;
-
-    case GSK_INSET_SHADOW_NODE:
-      if (gsk_inset_shadow_node_get_blur_radius (node) > 0)
-        gsk_ngl_render_job_visit_blurred_inset_shadow_node (job, node);
-      else
-        gsk_ngl_render_job_visit_unblurred_inset_shadow_node (job, node);
-    break;
-
-    case GSK_LINEAR_GRADIENT_NODE:
-    case GSK_REPEATING_LINEAR_GRADIENT_NODE:
-      if (gsk_linear_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
-        gsk_ngl_render_job_visit_linear_gradient_node (job, node);
-      else
-        gsk_ngl_render_job_visit_as_fallback (job, node);
-    break;
-
-    case GSK_OPACITY_NODE:
-      gsk_ngl_render_job_visit_opacity_node (job, node);
-    break;
-
-    case GSK_OUTSET_SHADOW_NODE:
-      if (gsk_outset_shadow_node_get_blur_radius (node) > 0)
-        gsk_ngl_render_job_visit_blurred_outset_shadow_node (job, node);
-      else
-        gsk_ngl_render_job_visit_unblurred_outset_shadow_node (job, node);
-    break;
-
-    case GSK_RADIAL_GRADIENT_NODE:
-    case GSK_REPEATING_RADIAL_GRADIENT_NODE:
-      if (gsk_radial_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS)
-        gsk_ngl_render_job_visit_radial_gradient_node (job, node);
-      else
-        gsk_ngl_render_job_visit_as_fallback (job, node);
-    break;
-
-    case GSK_REPEAT_NODE:
-      gsk_ngl_render_job_visit_repeat_node (job, node);
-    break;
-
-    case GSK_ROUNDED_CLIP_NODE:
-      gsk_ngl_render_job_visit_rounded_clip_node (job, node);
-    break;
-
-    case GSK_SHADOW_NODE:
-      gsk_ngl_render_job_visit_shadow_node (job, node);
-    break;
-
-    case GSK_TEXT_NODE:
-      gsk_ngl_render_job_visit_text_node (job,
-                                          node,
-                                          gsk_text_node_get_color (node),
-                                          FALSE);
-    break;
-
-    case GSK_TEXTURE_NODE:
-      gsk_ngl_render_job_visit_texture_node (job, node);
-    break;
-
-    case GSK_TRANSFORM_NODE:
-      gsk_ngl_render_job_visit_transform_node (job, node);
-    break;
-
-    case GSK_CAIRO_NODE:
-      gsk_ngl_render_job_visit_as_fallback (job, node);
-    break;
-
-    case GSK_NOT_A_RENDER_NODE:
-    default:
-      g_assert_not_reached ();
-    break;
-    }
-
-  if (has_clip)
-    gsk_ngl_render_job_pop_clip (job);
-}
-
-static gboolean
-gsk_ngl_render_job_visit_node_with_offscreen (GskNglRenderJob       *job,
-                                              const GskRenderNode   *node,
-                                              GskNglRenderOffscreen *offscreen)
-{
-  GskTextureKey key;
-  guint cached_id;
-  int filter;
-
-  g_assert (job != NULL);
-  g_assert (node != NULL);
-  g_assert (offscreen != NULL);
-  g_assert (offscreen->texture_id == 0);
-  g_assert (offscreen->bounds != NULL);
-
-  if (node_is_invisible (node))
-    {
-      /* Just to be safe. */
-      offscreen->texture_id = 0;
-      init_full_texture_region (offscreen);
-      offscreen->was_offscreen = FALSE;
-      return FALSE;
-    }
-
-  if (gsk_render_node_get_node_type (node) == GSK_TEXTURE_NODE &&
-      offscreen->force_offscreen == FALSE)
-    {
-      GdkTexture *texture = gsk_texture_node_get_texture (node);
-      gsk_ngl_render_job_upload_texture (job, texture, offscreen);
-      g_assert (offscreen->was_offscreen == FALSE);
-      return TRUE;
-    }
-
-  filter = offscreen->linear_filter ? GL_LINEAR : GL_NEAREST;
-
-  /* Check if we've already cached the drawn texture. */
-  key.pointer = node;
-  key.pointer_is_child = TRUE; /* Don't conflict with the child using the cache too */
-  key.parent_rect = *offscreen->bounds;
-  key.scale_x = job->scale_x;
-  key.scale_y = job->scale_y;
-  key.filter = filter;
-
-  cached_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
-
-  if (cached_id != 0)
-    {
-      offscreen->texture_id = cached_id;
-      init_full_texture_region (offscreen);
-      /* We didn't render it offscreen, but hand out an offscreen texture id */
-      offscreen->was_offscreen = TRUE;
-      return TRUE;
-    }
-
-  float scaled_width;
-  float scaled_height;
-  float downscale_x = 1;
-  float downscale_y = 1;
-
-  g_assert (job->command_queue->max_texture_size > 0);
-
-  /* Tweak the scale factor so that the required texture doesn't
-   * exceed the max texture limit. This will render with a lower
-   * resolution, but this is better than clipping.
-   */
-  {
-    int max_texture_size = job->command_queue->max_texture_size;
-
-    scaled_width = ceilf (offscreen->bounds->size.width * fabs (job->scale_x));
-    if (scaled_width > max_texture_size)
-      {
-        downscale_x = (float)max_texture_size / scaled_width;
-        scaled_width = max_texture_size;
-      }
-    if (job->scale_x < 0)
-      downscale_x = -downscale_x;
-
-    scaled_height = ceilf (offscreen->bounds->size.height * fabs (job->scale_y));
-    if (scaled_height > max_texture_size)
-      {
-        downscale_y = (float)max_texture_size / scaled_height;
-        scaled_height = max_texture_size;
-      }
-    if (job->scale_y < 0)
-      downscale_y = -downscale_y;
-  }
-
-  GskNglRenderTarget *render_target;
-  graphene_matrix_t prev_projection;
-  graphene_rect_t prev_viewport;
-  graphene_rect_t viewport;
-  float offset_x = job->offset_x;
-  float offset_y = job->offset_y;
-  float prev_alpha;
-  guint prev_fbo;
-
-  if (!gsk_ngl_driver_create_render_target (job->driver,
-                                             scaled_width, scaled_height,
-                                             get_target_format (job, node),
-                                             filter, filter,
-                                             &render_target))
-    g_assert_not_reached ();
-
-  if (gdk_gl_context_has_debug (job->command_queue->context))
-    {
-      gdk_gl_context_label_object_printf (job->command_queue->context,
-                                          GL_TEXTURE,
-                                          render_target->texture_id,
-                                          "Offscreen<%s> %d",
-                                          g_type_name_from_instance ((GTypeInstance *) node),
-                                          render_target->texture_id);
-      gdk_gl_context_label_object_printf (job->command_queue->context,
-                                          GL_FRAMEBUFFER,
-                                          render_target->framebuffer_id,
-                                          "Offscreen<%s> FB %d",
-                                          g_type_name_from_instance ((GTypeInstance *) node),
-                                          render_target->framebuffer_id);
-    }
-
-  if (downscale_x != 1 || downscale_y != 1)
-    {
-      GskTransform *transform = gsk_transform_scale (NULL, downscale_x, downscale_y);
-      gsk_ngl_render_job_push_modelview (job, transform);
-      gsk_transform_unref (transform);
-    }
-
-  gsk_ngl_render_job_transform_bounds (job, offscreen->bounds, &viewport);
-  /* Code above will scale the size with the scale we use in the render ops,
-   * but for the viewport size, we need our own size limited by the texture size */
-  viewport.size.width = scaled_width;
-  viewport.size.height = scaled_height;
-
-  gsk_ngl_render_job_set_viewport (job, &viewport, &prev_viewport);
-  gsk_ngl_render_job_set_projection_from_rect (job, &job->viewport, &prev_projection);
-  prev_alpha = gsk_ngl_render_job_set_alpha (job, 1.0f);
-
-  prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-  if (offscreen->reset_clip)
-    gsk_ngl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport));
-
-  gsk_ngl_render_job_visit_node (job, node);
-
-  if (offscreen->reset_clip)
-    gsk_ngl_render_job_pop_clip (job);
-
-  if (downscale_x != 1 || downscale_y != 1)
-    gsk_ngl_render_job_pop_modelview (job);
-  gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
-  gsk_ngl_render_job_set_projection (job, &prev_projection);
-  gsk_ngl_render_job_set_alpha (job, prev_alpha);
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
-
-  job->offset_x = offset_x;
-  job->offset_y = offset_y;
-
-  offscreen->was_offscreen = TRUE;
-  offscreen->texture_id = gsk_ngl_driver_release_render_target (job->driver,
-                                                                render_target,
-                                                                FALSE);
-
-  init_full_texture_region (offscreen);
-
-  if (!offscreen->do_not_cache)
-    gsk_ngl_driver_cache_texture (job->driver, &key, offscreen->texture_id);
-
-  return TRUE;
-}
-
-void
-gsk_ngl_render_job_render_flipped (GskNglRenderJob *job,
-                                   GskRenderNode   *root)
-{
-  graphene_matrix_t proj;
-  guint framebuffer_id;
-  guint texture_id;
-  guint surface_height;
-
-  g_return_if_fail (job != NULL);
-  g_return_if_fail (root != NULL);
-  g_return_if_fail (GSK_IS_NGL_DRIVER (job->driver));
-
-  surface_height = job->viewport.size.height;
-
-  graphene_matrix_init_ortho (&proj,
-                              job->viewport.origin.x,
-                              job->viewport.origin.x + job->viewport.size.width,
-                              job->viewport.origin.y,
-                              job->viewport.origin.y + job->viewport.size.height,
-                              ORTHO_NEAR_PLANE,
-                              ORTHO_FAR_PLANE);
-  graphene_matrix_scale (&proj, 1, -1, 1);
-
-  if (!gsk_ngl_command_queue_create_render_target (job->command_queue,
-                                                  MAX (1, job->viewport.size.width),
-                                                  MAX (1, job->viewport.size.height),
-                                                  job->target_format,
-                                                  GL_NEAREST, GL_NEAREST,
-                                                  &framebuffer_id, &texture_id))
-    return;
-
-  /* Setup drawing to our offscreen texture/framebuffer which is flipped */
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, framebuffer_id);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-
-  /* Visit all nodes creating batches */
-  gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue");
-  gsk_ngl_render_job_visit_node (job, root);
-  gdk_gl_context_pop_debug_group (job->command_queue->context);
-
-  /* Now draw to our real destination, but flipped */
-  gsk_ngl_render_job_set_alpha (job, 1.0f);
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-  gsk_ngl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit));
-  gsk_ngl_program_set_uniform_texture (job->current_program,
-                                       UNIFORM_SHARED_SOURCE, 0,
-                                       GL_TEXTURE_2D,
-                                       GL_TEXTURE0,
-                                       texture_id);
-  gsk_ngl_render_job_draw_rect (job, &job->viewport);
-  gsk_ngl_render_job_end_draw (job);
-
-  gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
-  gsk_ngl_command_queue_execute (job->command_queue, surface_height, 1, NULL);
-  gdk_gl_context_pop_debug_group (job->command_queue->context);
-
-  glDeleteFramebuffers (1, &framebuffer_id);
-  glDeleteTextures (1, &texture_id);
-}
-
-void
-gsk_ngl_render_job_render (GskNglRenderJob *job,
-                           GskRenderNode   *root)
-{
-  G_GNUC_UNUSED gint64 start_time;
-  guint scale_factor;
-  guint surface_height;
-
-  g_return_if_fail (job != NULL);
-  g_return_if_fail (root != NULL);
-  g_return_if_fail (GSK_IS_NGL_DRIVER (job->driver));
-
-  scale_factor = MAX (job->scale_x, job->scale_y);
-  surface_height = job->viewport.size.height;
-
-  gsk_ngl_command_queue_make_current (job->command_queue);
-
-  /* Build the command queue using the shared GL context for all renderers
-   * on the same display.
-   */
-  start_time = GDK_PROFILER_CURRENT_TIME;
-  gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue");
-  gsk_ngl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
-  gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
-  gsk_ngl_render_job_visit_node (job, root);
-  gdk_gl_context_pop_debug_group (job->command_queue->context);
-  gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Build GL command queue", "");
-
-#if 0
-  /* At this point the atlases have uploaded content while we processed
-   * nodes but have not necessarily been used by the commands in the queue.
-   */
-  gsk_ngl_driver_save_atlases_to_png (job->driver, NULL);
-#endif
-
-  /* But now for executing the command queue, we want to use the context
-   * that was provided to us when creating the render job as framebuffer 0
-   * is bound to that context.
-   */
-  start_time = GDK_PROFILER_CURRENT_TIME;
-  gsk_ngl_command_queue_make_current (job->command_queue);
-  gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
-  gsk_ngl_command_queue_execute (job->command_queue, surface_height, scale_factor, job->region);
-  gdk_gl_context_pop_debug_group (job->command_queue->context);
-  gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Execute GL command queue", "");
-}
-
-void
-gsk_ngl_render_job_set_debug_fallback (GskNglRenderJob *job,
-                                       gboolean         debug_fallback)
-{
-  g_return_if_fail (job != NULL);
-
-  job->debug_fallback = !!debug_fallback;
-}
-
-static int
-get_framebuffer_format (guint framebuffer)
-{
-  int size;
-
-  glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
-  glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER,  GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &size);
-
-  if (size >= 32)
-    return GL_RGBA32F;
-  else if (size >= 16)
-    return GL_RGBA16F;
-  else
-    return GL_RGBA8;
-}
-
-GskNglRenderJob *
-gsk_ngl_render_job_new (GskNglDriver          *driver,
-                        const graphene_rect_t *viewport,
-                        float                  scale_factor,
-                        const cairo_region_t  *region,
-                        guint                  framebuffer)
-{
-  const graphene_rect_t *clip_rect = viewport;
-  graphene_rect_t transformed_extents;
-  GskNglRenderJob *job;
-
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-  g_return_val_if_fail (viewport != NULL, NULL);
-  g_return_val_if_fail (scale_factor > 0, NULL);
-
-  job = g_slice_new0 (GskNglRenderJob);
-  job->driver = g_object_ref (driver);
-  job->command_queue = job->driver->command_queue;
-  job->clip = g_array_sized_new (FALSE, FALSE, sizeof (GskNglRenderClip), 16);
-  job->modelview = g_array_sized_new (FALSE, FALSE, sizeof (GskNglRenderModelview), 16);
-  job->framebuffer = framebuffer;
-  job->offset_x = 0;
-  job->offset_y = 0;
-  job->scale_x = scale_factor;
-  job->scale_y = scale_factor;
-  job->viewport = *viewport;
-  job->target_format = get_framebuffer_format (framebuffer);
-
-  gsk_ngl_render_job_set_alpha (job, 1.0f);
-  gsk_ngl_render_job_set_projection_from_rect (job, viewport, NULL);
-  gsk_ngl_render_job_set_modelview (job, gsk_transform_scale (NULL, scale_factor, scale_factor));
-
-  /* Setup our initial clip. If region is NULL then we are drawing the
-   * whole viewport. Otherwise, we need to convert the region to a
-   * bounding box and clip based on that.
-   */
-
-  if (region != NULL)
-    {
-      cairo_rectangle_int_t extents;
-
-      cairo_region_get_extents (region, &extents);
-      gsk_ngl_render_job_transform_bounds (job,
-                                           &GRAPHENE_RECT_INIT (extents.x,
-                                                                extents.y,
-                                                                extents.width,
-                                                                extents.height),
-                                           &transformed_extents);
-      clip_rect = &transformed_extents;
-      job->region = cairo_region_create_rectangle (&extents);
-    }
-
-  gsk_ngl_render_job_push_clip (job,
-                                &GSK_ROUNDED_RECT_INIT (clip_rect->origin.x,
-                                                        clip_rect->origin.y,
-                                                        clip_rect->size.width,
-                                                        clip_rect->size.height));
-
-  return job;
-}
-
-void
-gsk_ngl_render_job_free (GskNglRenderJob *job)
-{
-  job->current_modelview = NULL;
-  job->current_clip = NULL;
-
-  while (job->modelview->len > 0)
-    {
-      GskNglRenderModelview *modelview = &g_array_index (job->modelview, GskNglRenderModelview, job->modelview->len-1);
-      g_clear_pointer (&modelview->transform, gsk_transform_unref);
-      job->modelview->len--;
-    }
-
-  g_clear_object (&job->driver);
-  g_clear_pointer (&job->region, cairo_region_destroy);
-  g_clear_pointer (&job->modelview, g_array_unref);
-  g_clear_pointer (&job->clip, g_array_unref);
-  g_slice_free (GskNglRenderJob, job);
-}
diff --git a/gsk/ngl/gsknglrenderjobprivate.h b/gsk/ngl/gsknglrenderjobprivate.h
deleted file mode 100644 (file)
index ba3f3e4..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* gsknglrenderjobprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_RENDER_JOB_H__
-#define __GSK_NGL_RENDER_JOB_H__
-
-#include "gskngltypesprivate.h"
-
-GskNglRenderJob *gsk_ngl_render_job_new                (GskNglDriver          *driver,
-                                                        const graphene_rect_t *viewport,
-                                                        float                  scale_factor,
-                                                        const cairo_region_t  *region,
-                                                        guint                  framebuffer);
-void             gsk_ngl_render_job_free               (GskNglRenderJob       *job);
-void             gsk_ngl_render_job_render             (GskNglRenderJob       *job,
-                                                        GskRenderNode         *root);
-void             gsk_ngl_render_job_render_flipped     (GskNglRenderJob       *job,
-                                                        GskRenderNode         *root);
-void             gsk_ngl_render_job_set_debug_fallback (GskNglRenderJob       *job,
-                                                        gboolean               debug_fallback);
-
-#endif /* __GSK_NGL_RENDER_JOB_H__ */
diff --git a/gsk/ngl/gsknglshadowlibrary.c b/gsk/ngl/gsknglshadowlibrary.c
deleted file mode 100644 (file)
index 64fb45b..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-/* gsknglshadowlibrary.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <string.h>
-
-#include "gskngldriverprivate.h"
-#include "gsknglshadowlibraryprivate.h"
-
-#define MAX_UNUSED_FRAMES (16 * 5)
-
-struct _GskNglShadowLibrary
-{
-  GObject        parent_instance;
-  GskNglDriver *driver;
-  GArray        *shadows;
-};
-
-typedef struct _Shadow
-{
-  GskRoundedRect outline;
-  float          blur_radius;
-  guint          texture_id;
-  gint64         last_used_in_frame;
-} Shadow;
-
-G_DEFINE_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, G_TYPE_OBJECT)
-
-enum {
-  PROP_0,
-  PROP_DRIVER,
-  N_PROPS
-};
-
-static GParamSpec *properties [N_PROPS];
-
-GskNglShadowLibrary *
-gsk_ngl_shadow_library_new (GskNglDriver *driver)
-{
-  g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
-
-  return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY,
-                       "driver", driver,
-                       NULL);
-}
-
-static void
-gsk_ngl_shadow_library_dispose (GObject *object)
-{
-  GskNglShadowLibrary *self = (GskNglShadowLibrary *)object;
-
-  for (guint i = 0; i < self->shadows->len; i++)
-    {
-      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
-      gsk_ngl_driver_release_texture_by_id (self->driver, shadow->texture_id);
-    }
-
-  g_clear_pointer (&self->shadows, g_array_unref);
-  g_clear_object (&self->driver);
-
-  G_OBJECT_CLASS (gsk_ngl_shadow_library_parent_class)->dispose (object);
-}
-
-static void
-gsk_ngl_shadow_library_get_property (GObject    *object,
-                                     guint       prop_id,
-                                     GValue     *value,
-                                     GParamSpec *pspec)
-{
-  GskNglShadowLibrary *self = GSK_NGL_SHADOW_LIBRARY (object);
-
-  switch (prop_id)
-    {
-    case PROP_DRIVER:
-      g_value_set_object (value, self->driver);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-gsk_ngl_shadow_library_set_property (GObject      *object,
-                                     guint         prop_id,
-                                     const GValue *value,
-                                     GParamSpec   *pspec)
-{
-  GskNglShadowLibrary *self = GSK_NGL_SHADOW_LIBRARY (object);
-
-  switch (prop_id)
-    {
-    case PROP_DRIVER:
-      self->driver = g_value_dup_object (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-gsk_ngl_shadow_library_class_init (GskNglShadowLibraryClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->dispose = gsk_ngl_shadow_library_dispose;
-  object_class->get_property = gsk_ngl_shadow_library_get_property;
-  object_class->set_property = gsk_ngl_shadow_library_set_property;
-
-  properties [PROP_DRIVER] =
-    g_param_spec_object ("driver",
-                         "Driver",
-                         "Driver",
-                         GSK_TYPE_NGL_DRIVER,
-                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_properties (object_class, N_PROPS, properties);
-}
-
-static void
-gsk_ngl_shadow_library_init (GskNglShadowLibrary *self)
-{
-  self->shadows = g_array_new (FALSE, FALSE, sizeof (Shadow));
-}
-
-void
-gsk_ngl_shadow_library_insert (GskNglShadowLibrary  *self,
-                               const GskRoundedRect *outline,
-                               float                 blur_radius,
-                               guint                 texture_id)
-{
-  Shadow *shadow;
-
-  g_assert (GSK_IS_NGL_SHADOW_LIBRARY (self));
-  g_assert (outline != NULL);
-  g_assert (texture_id != 0);
-
-  gsk_ngl_driver_mark_texture_permanent (self->driver, texture_id);
-
-  g_array_set_size (self->shadows, self->shadows->len + 1);
-
-  shadow = &g_array_index (self->shadows, Shadow, self->shadows->len - 1);
-  shadow->outline = *outline;
-  shadow->blur_radius = blur_radius;
-  shadow->texture_id = texture_id;
-  shadow->last_used_in_frame = self->driver->current_frame_id;
-}
-
-guint
-gsk_ngl_shadow_library_lookup (GskNglShadowLibrary  *self,
-                               const GskRoundedRect *outline,
-                               float                 blur_radius)
-{
-  Shadow *ret = NULL;
-
-  g_assert (GSK_IS_NGL_SHADOW_LIBRARY (self));
-  g_assert (outline != NULL);
-
-  /* Ensure GskRoundedRect  is 12 packed floats without padding
-   * so that we can use memcmp instead of float comparisons.
-   */
-  G_STATIC_ASSERT (sizeof *outline == (sizeof (float) * 12));
-
-  for (guint i = 0; i < self->shadows->len; i++)
-    {
-      Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
-
-      if (blur_radius == shadow->blur_radius &&
-          memcmp (outline, &shadow->outline, sizeof *outline) == 0)
-        {
-          ret = shadow;
-          break;
-        }
-    }
-
-  if (ret == NULL)
-    return 0;
-
-  g_assert (ret->texture_id != 0);
-
-  ret->last_used_in_frame = self->driver->current_frame_id;
-
-  return ret->texture_id;
-}
-
-#if 0
-static void
-write_shadow_to_png (GskNglDriver *driver,
-                     const Shadow *shadow)
-{
-  int width = shadow->outline.bounds.size.width + (shadow->outline.bounds.origin.x * 2);
-  int height = shadow->outline.bounds.size.height + (shadow->outline.bounds.origin.y * 2);
-  char *filename = g_strdup_printf ("shadow_cache_%d_%d_%d.png",
-                                    width, height, shadow->texture_id);
-  GdkTexture *texture;
-
-  texture = gdk_gl_texture_new (gsk_ngl_driver_get_context (driver),
-                                shadow->texture_id,
-                                width, height,
-                                NULL, NULL);
-  gdk_texture_save_to_png (texture, filename);
-
-  g_object_unref (texture);
-  g_free (filename);
-}
-#endif
-
-void
-gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary *self)
-{
-  gint64 watermark;
-  int i;
-  int p;
-
-  g_return_if_fail (GSK_IS_NGL_SHADOW_LIBRARY (self));
-
-#if 0
-  for (i = 0, p = self->shadows->len; i < p; i++)
-    {
-      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
-      write_shadow_to_png (self->driver, shadow);
-    }
-#endif
-
-  watermark = self->driver->current_frame_id - MAX_UNUSED_FRAMES;
-
-  for (i = 0, p = self->shadows->len; i < p; i++)
-    {
-      const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
-
-      if (shadow->last_used_in_frame < watermark)
-        {
-          gsk_ngl_driver_release_texture_by_id (self->driver, shadow->texture_id);
-          g_array_remove_index_fast (self->shadows, i);
-          p--;
-          i--;
-        }
-    }
-}
diff --git a/gsk/ngl/gsknglshadowlibraryprivate.h b/gsk/ngl/gsknglshadowlibraryprivate.h
deleted file mode 100644 (file)
index 3c53466..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/* gsknglshadowlibraryprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
-#define __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
-
-#include "gskngltexturelibraryprivate.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_ngl_shadow_library_get_type())
-
-G_DECLARE_FINAL_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, GSK, NGL_SHADOW_LIBRARY, GObject)
-
-GskNglShadowLibrary *gsk_ngl_shadow_library_new         (GskNglDriver         *driver);
-void                 gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary  *self);
-guint                gsk_ngl_shadow_library_lookup      (GskNglShadowLibrary  *self,
-                                                         const GskRoundedRect *outline,
-                                                         float                 blur_radius);
-void                 gsk_ngl_shadow_library_insert      (GskNglShadowLibrary  *self,
-                                                         const GskRoundedRect *outline,
-                                                         float                 blur_radius,
-                                                         guint                 texture_id);
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltexture.c b/gsk/ngl/gskngltexture.c
deleted file mode 100644 (file)
index c70d4d9..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/* gskngltexture.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdktextureprivate.h>
-
-#include "gskngltextureprivate.h"
-#include "ninesliceprivate.h"
-
-void
-gsk_ngl_texture_free (GskNglTexture *texture)
-{
-  if (texture != NULL)
-    {
-      g_assert (texture->link.prev == NULL);
-      g_assert (texture->link.next == NULL);
-
-      if (texture->user)
-        g_clear_pointer (&texture->user, gdk_texture_clear_render_data);
-
-      if (texture->texture_id != 0)
-        {
-          glDeleteTextures (1, &texture->texture_id);
-          texture->texture_id = 0;
-        }
-
-      for (guint i = 0; i < texture->n_slices; i++)
-        {
-          glDeleteTextures (1, &texture->slices[i].texture_id);
-          texture->slices[i].texture_id = 0;
-        }
-
-      g_clear_pointer (&texture->slices, g_free);
-      g_clear_pointer (&texture->nine_slice, g_free);
-
-      g_slice_free (GskNglTexture, texture);
-    }
-}
-
-GskNglTexture *
-gsk_ngl_texture_new (guint  texture_id,
-                     int    width,
-                     int    height,
-                     int    format,
-                     int    min_filter,
-                     int    mag_filter,
-                     gint64 frame_id)
-{
-  GskNglTexture *texture;
-
-  texture = g_slice_new0 (GskNglTexture);
-  texture->texture_id = texture_id;
-  texture->link.data = texture;
-  texture->min_filter = min_filter;
-  texture->mag_filter = mag_filter;
-  texture->format = format;
-  texture->width = width;
-  texture->height = height;
-  texture->last_used_in_frame = frame_id;
-
-  return texture;
-}
-
-const GskNglTextureNineSlice *
-gsk_ngl_texture_get_nine_slice (GskNglTexture        *texture,
-                                const GskRoundedRect *outline,
-                                float                 extra_pixels_x,
-                                float                 extra_pixels_y)
-{
-  g_assert (texture != NULL);
-  g_assert (outline != NULL);
-
-  if G_UNLIKELY (texture->nine_slice == NULL)
-    {
-      texture->nine_slice = g_new0 (GskNglTextureNineSlice, 9);
-
-      nine_slice_rounded_rect (texture->nine_slice, outline);
-      nine_slice_grow (texture->nine_slice, extra_pixels_x, extra_pixels_y);
-      nine_slice_to_texture_coords (texture->nine_slice, texture->width, texture->height);
-    }
-
-  return texture->nine_slice;
-}
diff --git a/gsk/ngl/gskngltexturelibrary.c b/gsk/ngl/gskngltexturelibrary.c
deleted file mode 100644 (file)
index 03b8e25..0000000
+++ /dev/null
@@ -1,426 +0,0 @@
-/* gskngltexturelibrary.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gdk/gdkglcontextprivate.h>
-#include <gsk/gskdebugprivate.h>
-
-#include "gsknglcommandqueueprivate.h"
-#include "gskngldriverprivate.h"
-#include "gskngltexturelibraryprivate.h"
-
-#define MAX_FRAME_AGE 60
-
-G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT)
-
-enum {
-  PROP_0,
-  PROP_DRIVER,
-  N_PROPS
-};
-
-static GParamSpec *properties [N_PROPS];
-
-static void
-gsk_ngl_texture_library_constructed (GObject *object)
-{
-  G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->constructed (object);
-
-  g_assert (GSK_NGL_TEXTURE_LIBRARY (object)->hash_table != NULL);
-}
-
-static void
-gsk_ngl_texture_library_dispose (GObject *object)
-{
-  GskNglTextureLibrary *self = (GskNglTextureLibrary *)object;
-
-  g_clear_object (&self->driver);
-
-  G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->dispose (object);
-}
-
-static void
-gsk_ngl_texture_library_get_property (GObject    *object,
-                                      guint       prop_id,
-                                      GValue     *value,
-                                      GParamSpec *pspec)
-{
-  GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
-
-  switch (prop_id)
-    {
-    case PROP_DRIVER:
-      g_value_set_object (value, self->driver);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-gsk_ngl_texture_library_set_property (GObject      *object,
-                                      guint         prop_id,
-                                      const GValue *value,
-                                      GParamSpec   *pspec)
-{
-  GskNglTextureLibrary *self = GSK_NGL_TEXTURE_LIBRARY (object);
-
-  switch (prop_id)
-    {
-    case PROP_DRIVER:
-      self->driver = g_value_dup_object (value);
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-gsk_ngl_texture_library_class_init (GskNglTextureLibraryClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-  object_class->constructed = gsk_ngl_texture_library_constructed;
-  object_class->dispose = gsk_ngl_texture_library_dispose;
-  object_class->get_property = gsk_ngl_texture_library_get_property;
-  object_class->set_property = gsk_ngl_texture_library_set_property;
-
-  properties [PROP_DRIVER] =
-    g_param_spec_object ("driver",
-                         "Driver",
-                         "Driver",
-                         GSK_TYPE_NGL_DRIVER,
-                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_properties (object_class, N_PROPS, properties);
-}
-
-static void
-gsk_ngl_texture_library_init (GskNglTextureLibrary *self)
-{
-}
-
-void
-gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
-                                   GHashFunc             hash_func,
-                                   GEqualFunc            equal_func,
-                                   GDestroyNotify        key_destroy,
-                                   GDestroyNotify        value_destroy)
-{
-  g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
-  g_return_if_fail (self->hash_table == NULL);
-
-  self->hash_table = g_hash_table_new_full (hash_func, equal_func,
-                                            key_destroy, value_destroy);
-}
-
-void
-gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self,
-                                     gint64                frame_id,
-                                     GPtrArray            *removed_atlases)
-{
-  GHashTableIter iter;
-
-  g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
-
-  if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
-    GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id, removed_atlases);
-
-  if (removed_atlases != NULL)
-    {
-      GskNglTextureAtlasEntry *entry;
-      guint dropped = 0;
-
-      g_hash_table_iter_init (&iter, self->hash_table);
-      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
-        {
-          if (entry->is_atlased)
-            {
-              for (guint i = 0; i < removed_atlases->len; i++)
-                {
-                  GskNglTextureAtlas *atlas = g_ptr_array_index (removed_atlases, i);
-
-                  if (atlas == entry->atlas)
-                    {
-                      g_hash_table_iter_remove (&iter);
-                      dropped++;
-                      break;
-                    }
-                }
-            }
-        }
-
-      GSK_NOTE (GLYPH_CACHE,
-                if (dropped > 0)
-                  g_message ("%s: Dropped %d items",
-                             G_OBJECT_TYPE_NAME (self), dropped));
-    }
-
-  if (frame_id % MAX_FRAME_AGE == 0)
-    {
-      GskNglTextureAtlasEntry *entry;
-      int atlased = 0;
-      int dropped = 0;
-
-      g_hash_table_iter_init (&iter, self->hash_table);
-      while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry))
-        {
-          if (!entry->is_atlased && !entry->accessed)
-            {
-              gsk_ngl_driver_release_texture (self->driver, entry->texture);
-              g_hash_table_iter_remove (&iter);
-              dropped++;
-              continue;
-            }
-
-          gsk_ngl_texture_atlas_entry_mark_unused (entry);
-          entry->accessed = FALSE;
-          if (entry->is_atlased)
-            atlased++;
-        }
-
-      GSK_NOTE (GLYPH_CACHE, g_message ("%s: Dropped %d individual items",
-                                        G_OBJECT_TYPE_NAME (self),
-                                        dropped);
-                             g_message ("%s: %d items cached (%d atlased, %d individually)",
-                                        G_OBJECT_TYPE_NAME (self),
-                                        g_hash_table_size (self->hash_table),
-                                        atlased,
-                                        g_hash_table_size (self->hash_table) - atlased));
-    }
-}
-
-static GskNglTexture *
-gsk_ngl_texture_library_pack_one (GskNglTextureLibrary *self,
-                                  guint                 width,
-                                  guint                 height)
-{
-  GskNglTexture *texture;
-
-  g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
-
-  if (width > self->driver->command_queue->max_texture_size ||
-      height > self->driver->command_queue->max_texture_size)
-    {
-      g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.",
-                 width, height, self->driver->command_queue->max_texture_size);
-      width = MIN (width, self->driver->command_queue->max_texture_size);
-      height = MIN (height, self->driver->command_queue->max_texture_size);
-    }
-
-  texture = gsk_ngl_driver_create_texture (self->driver, width, height, GL_RGBA8, GL_LINEAR, GL_LINEAR);
-  texture->permanent = TRUE;
-
-  return texture;
-}
-
-static inline gboolean
-gsk_ngl_texture_atlas_pack (GskNglTextureAtlas *self,
-                            int                 width,
-                            int                 height,
-                            int                *out_x,
-                            int                *out_y)
-{
-  stbrp_rect rect;
-
-  rect.w = width;
-  rect.h = height;
-
-  stbrp_pack_rects (&self->context, &rect, 1);
-
-  if (rect.was_packed)
-    {
-      *out_x = rect.x;
-      *out_y = rect.y;
-    }
-
-  return rect.was_packed;
-}
-
-static void
-gsk_ngl_texture_atlas_initialize (GskNglDriver       *driver,
-                                  GskNglTextureAtlas *atlas)
-{
-  /* Insert a single pixel at 0,0 for use in coloring */
-
-  gboolean packed G_GNUC_UNUSED;
-  int x, y;
-  guint gl_format;
-  guint gl_type;
-  guint8 pixel_data[4 * 3 * 3];
-
-  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
-                                          "Initializing Atlas");
-
-  packed = gsk_ngl_texture_atlas_pack (atlas, 3, 3, &x, &y);
-  g_assert (packed);
-  g_assert (x == 0 && y == 0);
-
-  memset (pixel_data, 255, sizeof pixel_data);
-
-  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
-    {
-      gl_format = GL_RGBA;
-      gl_type = GL_UNSIGNED_BYTE;
-    }
-  else
-    {
-      gl_format = GL_BGRA;
-      gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
-    }
-
-  glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
-
-  glTexSubImage2D (GL_TEXTURE_2D, 0,
-                   0, 0,
-                   3, 3,
-                   gl_format, gl_type,
-                   pixel_data);
-
-  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
-
-  driver->command_queue->n_uploads++;
-}
-
-static void
-gsk_ngl_texture_atlases_pack (GskNglDriver        *driver,
-                              int                  width,
-                              int                  height,
-                              GskNglTextureAtlas **out_atlas,
-                              int                 *out_x,
-                              int                 *out_y)
-{
-  GskNglTextureAtlas *atlas = NULL;
-  int x, y;
-
-  for (guint i = 0; i < driver->atlases->len; i++)
-    {
-      atlas = g_ptr_array_index (driver->atlases, i);
-
-      if (gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
-        break;
-
-      atlas = NULL;
-    }
-
-  if (atlas == NULL)
-    {
-      /* No atlas has enough space, so create a new one... */
-      atlas = gsk_ngl_driver_create_atlas (driver);
-
-      gsk_ngl_texture_atlas_initialize (driver, atlas);
-
-      /* Pack it onto that one, which surely has enough space... */
-      if (!gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
-        g_assert_not_reached ();
-    }
-
-  *out_atlas = atlas;
-  *out_x = x;
-  *out_y = y;
-}
-
-gpointer
-gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
-                              gpointer              key,
-                              gsize                 valuelen,
-                              guint                 width,
-                              guint                 height,
-                              int                   padding,
-                              guint                *out_packed_x,
-                              guint                *out_packed_y)
-{
-  GskNglTextureAtlasEntry *entry;
-  GskNglTextureAtlas *atlas = NULL;
-
-  g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
-  g_assert (key != NULL);
-  g_assert (valuelen > sizeof (GskNglTextureAtlasEntry));
-  g_assert (out_packed_x != NULL);
-  g_assert (out_packed_y != NULL);
-
-  entry = g_slice_alloc0 (valuelen);
-  entry->n_pixels = width * height;
-  entry->accessed = TRUE;
-  entry->used = TRUE;
-
-  /* If our size is invisible then we just want an entry in the
-   * cache for faster lookups, but do not actually spend any texture
-   * allocations on this entry.
-   */
-  if (width <= 0 && height <= 0)
-    {
-      entry->is_atlased = FALSE;
-      entry->texture = NULL;
-      entry->area.x = 0.0f;
-      entry->area.y = 0.0f;
-      entry->area.x2 = 0.0f;
-      entry->area.y2 = 0.0f;
-
-      *out_packed_x = 0;
-      *out_packed_y = 0;
-    }
-  else if (width <= self->max_entry_size && height <= self->max_entry_size)
-    {
-      int packed_x;
-      int packed_y;
-
-      gsk_ngl_texture_atlases_pack (self->driver,
-                                    padding + width + padding,
-                                    padding + height + padding,
-                                    &atlas,
-                                    &packed_x,
-                                    &packed_y);
-
-      entry->atlas = atlas;
-      entry->is_atlased = TRUE;
-      entry->area.x = (packed_x + padding) / (float)atlas->width;
-      entry->area.y = (packed_y + padding) / (float)atlas->height;
-      entry->area.x2 = (packed_x + padding + width) / (float)atlas->width;
-      entry->area.y2 = (packed_y + padding + height) / (float)atlas->height;
-
-      *out_packed_x = packed_x;
-      *out_packed_y = packed_y;
-    }
-  else
-    {
-      GskNglTexture *texture = gsk_ngl_texture_library_pack_one (self,
-                                                                 padding + width + padding,
-                                                                 padding + height + padding);
-
-      entry->texture = texture;
-      entry->is_atlased = FALSE;
-      entry->accessed = TRUE;
-      entry->area.x = padding / (float) (padding + width + padding);
-      entry->area.y = padding / (float) (padding + height + padding);
-      entry->area.x2 = (padding + width) / (float) (padding + width + padding);
-      entry->area.y2 = (padding + height) / (float) (padding + height + padding);
-
-      *out_packed_x = 0;
-      *out_packed_y = 0;
-    }
-
-  g_hash_table_insert (self->hash_table, key, entry);
-
-  return entry;
-}
diff --git a/gsk/ngl/gskngltexturelibraryprivate.h b/gsk/ngl/gskngltexturelibraryprivate.h
deleted file mode 100644 (file)
index 0ccf69a..0000000
+++ /dev/null
@@ -1,208 +0,0 @@
-/* gskngltexturelibraryprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
-#define __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-#include "gskngltextureprivate.h"
-
-#include "stb_rect_pack.h"
-
-G_BEGIN_DECLS
-
-#define GSK_TYPE_GL_TEXTURE_LIBRARY            (gsk_ngl_texture_library_get_type ())
-#define GSK_NGL_TEXTURE_LIBRARY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibrary))
-#define GSK_IS_NGL_TEXTURE_LIBRARY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY))
-#define GSK_NGL_TEXTURE_LIBRARY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
-#define GSK_IS_NGL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY))
-#define GSK_NGL_TEXTURE_LIBRARY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
-
-typedef struct _GskNglTextureAtlas
-{
-  struct stbrp_context context;
-  struct stbrp_node *nodes;
-
-  int width;
-  int height;
-
-  guint texture_id;
-
-  /* Pixels of rects that have been used at some point,
-   * But are now unused.
-   */
-  int unused_pixels;
-
-  void *user_data;
-} GskNglTextureAtlas;
-
-typedef struct _GskNglTextureAtlasEntry
-{
-  /* A backreference to either the atlas or texture containing
-   * the contents of the atlas entry. For larger items, no atlas
-   * is used and instead a direct texture.
-   */
-  union {
-    GskNglTextureAtlas *atlas;
-    GskNglTexture *texture;
-  };
-
-  /* The area within the atlas translated to 0..1 bounds */
-  struct {
-    float x;
-    float y;
-    float x2;
-    float y2;
-  } area;
-
-  /* Number of pixels in the entry, used to calculate usage
-   * of an atlas while processing.
-   */
-  guint n_pixels : 29;
-
-  /* If entry has marked pixels as used in the atlas this frame */
-  guint used : 1;
-
-  /* If entry was accessed this frame */
-  guint accessed : 1;
-
-  /* When true, backref is an atlas, otherwise texture */
-  guint is_atlased : 1;
-} GskNglTextureAtlasEntry;
-
-typedef struct _GskNglTextureLibrary
-{
-  GObject        parent_instance;
-  GskNglDriver *driver;
-  GHashTable    *hash_table;
-  guint          max_entry_size;
-} GskNglTextureLibrary;
-
-typedef struct _GskNglTextureLibraryClass
-{
-  GObjectClass parent_class;
-
-  void (*begin_frame) (GskNglTextureLibrary *library,
-                       gint64                frame_id,
-                       GPtrArray            *removed_atlases);
-} GskNglTextureLibraryClass;
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskNglTextureLibrary, g_object_unref)
-
-GType    gsk_ngl_texture_library_get_type    (void) G_GNUC_CONST;
-void     gsk_ngl_texture_library_set_funcs   (GskNglTextureLibrary *self,
-                                              GHashFunc             hash_func,
-                                              GEqualFunc            equal_func,
-                                              GDestroyNotify        key_destroy,
-                                              GDestroyNotify        value_destroy);
-void     gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self,
-                                              gint64                frame_id,
-                                              GPtrArray            *removed_atlases);
-gpointer gsk_ngl_texture_library_pack        (GskNglTextureLibrary *self,
-                                              gpointer              key,
-                                              gsize                 valuelen,
-                                              guint                 width,
-                                              guint                 height,
-                                              int                   padding,
-                                              guint                *out_packed_x,
-                                              guint                *out_packed_y);
-
-static inline void
-gsk_ngl_texture_atlas_mark_unused (GskNglTextureAtlas *self,
-                                   int                 n_pixels)
-{
-  g_assert (n_pixels >= 0);
-
-  self->unused_pixels += n_pixels;
-}
-
-static inline void
-gsk_ngl_texture_atlas_entry_mark_used (GskNglTextureAtlasEntry *entry)
-{
-  if (entry->used == TRUE || entry->is_atlased == FALSE)
-    return;
-
-  entry->atlas->unused_pixels -= entry->n_pixels;
-  entry->used = TRUE;
-}
-
-static inline void
-gsk_ngl_texture_atlas_entry_mark_unused (GskNglTextureAtlasEntry *entry)
-{
-  if (entry->used == FALSE || entry->is_atlased == FALSE)
-    return;
-
-  entry->atlas->unused_pixels += entry->n_pixels;
-  entry->used = FALSE;
-}
-
-static inline gboolean
-gsk_ngl_texture_library_lookup (GskNglTextureLibrary     *self,
-                                gconstpointer             key,
-                                GskNglTextureAtlasEntry **out_entry)
-{
-  GskNglTextureAtlasEntry *entry = g_hash_table_lookup (self->hash_table, key);
-
-  if G_LIKELY (entry != NULL && entry->accessed && entry->used)
-    {
-      *out_entry = entry;
-      return TRUE;
-    }
-
-  if (entry != NULL)
-    {
-      gsk_ngl_texture_atlas_entry_mark_used (entry);
-      entry->accessed = TRUE;
-      *out_entry = entry;
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-static inline guint
-GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (gconstpointer d)
-{
-  const GskNglTextureAtlasEntry *e = d;
-
-  return e->is_atlased ? e->atlas->texture_id
-                       : e->texture ? e->texture->texture_id : 0;
-}
-
-static inline double
-gsk_ngl_texture_atlas_get_unused_ratio (const GskNglTextureAtlas *self)
-{
-  if (self->unused_pixels > 0)
-    return (double)(self->unused_pixels) / (double)(self->width * self->height);
-  return 0.0;
-}
-
-static inline gboolean
-gsk_ngl_texture_library_can_cache (GskNglTextureLibrary *self,
-                                   int                   width,
-                                   int                   height)
-{
-  g_assert (self->max_entry_size > 0);
-  return width <= self->max_entry_size && height <= self->max_entry_size;
-}
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltextureprivate.h b/gsk/ngl/gskngltextureprivate.h
deleted file mode 100644 (file)
index 55ef48c..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/* gskngltextureprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This file is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Lesser General Public License as published by the Free
- * Software Foundation; either version 2.1 of the License, or (at your option)
- * any later version.
- *
- * This file is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef _GSK_NGL_TEXTURE_PRIVATE_H__
-#define _GSK_NGL_TEXTURE_PRIVATE_H__
-
-#include "gskngltypesprivate.h"
-
-G_BEGIN_DECLS
-
-struct _GskNglTextureSlice
-{
-  cairo_rectangle_int_t rect;
-  guint texture_id;
-};
-
-struct _GskNglTextureNineSlice
-{
-  cairo_rectangle_int_t rect;
-  struct {
-    float x;
-    float y;
-    float x2;
-    float y2;
-  } area;
-};
-
-struct _GskNglTexture
-{
-  /* Used to insert into queue */
-  GList link;
-
-  /* Identifier of the frame that created it */
-  gint64 last_used_in_frame;
-
-  /* Backpointer to texture (can be cleared asynchronously) */
-  GdkTexture *user;
-
-  /* Only used by nine-slice textures */
-  GskNglTextureNineSlice *nine_slice;
-
-  /* Only used by sliced textures */
-  GskNglTextureSlice *slices;
-  guint n_slices;
-
-  /* The actual GL texture identifier in some shared context */
-  guint texture_id;
-
-  int width;
-  int height;
-  int min_filter;
-  int mag_filter;
-  int format;
-
-  /* Set when used by an atlas so we don't drop the texture */
-  guint              permanent : 1;
-};
-
-GskNglTexture                *gsk_ngl_texture_new            (guint                 texture_id,
-                                                              int                   width,
-                                                              int                   height,
-                                                              int                   format,
-                                                              int                   min_filter,
-                                                              int                   mag_filter,
-                                                              gint64                frame_id);
-const GskNglTextureNineSlice *gsk_ngl_texture_get_nine_slice (GskNglTexture        *texture,
-                                                              const GskRoundedRect *outline,
-                                                              float                 extra_pixels_x,
-                                                              float                 extra_pixels_y);
-void                          gsk_ngl_texture_free           (GskNglTexture        *texture);
-
-G_END_DECLS
-
-#endif /* _GSK_NGL_TEXTURE_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltypesprivate.h b/gsk/ngl/gskngltypesprivate.h
deleted file mode 100644 (file)
index ce2319e..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/* gskngltypesprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __GSK_NGL_TYPES_PRIVATE_H__
-#define __GSK_NGL_TYPES_PRIVATE_H__
-
-#include <epoxy/gl.h>
-#include <graphene.h>
-#include <gdk/gdk.h>
-#include <gsk/gsk.h>
-
-G_BEGIN_DECLS
-
-#define GSK_NGL_N_VERTICES 6
-
-typedef struct _GskNglAttachmentState GskNglAttachmentState;
-typedef struct _GskNglBuffer GskNglBuffer;
-typedef struct _GskNglCommandQueue GskNglCommandQueue;
-typedef struct _GskNglCompiler GskNglCompiler;
-typedef struct _GskNglDrawVertex GskNglDrawVertex;
-typedef struct _GskNglRenderTarget GskNglRenderTarget;
-typedef struct _GskNglGlyphLibrary GskNglGlyphLibrary;
-typedef struct _GskNglIconLibrary GskNglIconLibrary;
-typedef struct _GskNglProgram GskNglProgram;
-typedef struct _GskNglRenderJob GskNglRenderJob;
-typedef struct _GskNglShadowLibrary GskNglShadowLibrary;
-typedef struct _GskNglTexture GskNglTexture;
-typedef struct _GskNglTextureSlice GskNglTextureSlice;
-typedef struct _GskNglTextureAtlas GskNglTextureAtlas;
-typedef struct _GskNglTextureLibrary GskNglTextureLibrary;
-typedef struct _GskNglTextureNineSlice GskNglTextureNineSlice;
-typedef struct _GskNglUniformInfo GskNglUniformInfo;
-typedef struct _GskNglUniformProgram GskNglUniformProgram;
-typedef struct _GskNglUniformState GskNglUniformState;
-typedef struct _GskNglDriver GskNglDriver;
-
-struct _GskNglDrawVertex
-{
-  float position[2];
-  union {
-    float uv[2];
-    guint16 color2[4];
-  };
-  guint16 color[4];
-};
-
-G_END_DECLS
-
-#endif /* __GSK_NGL_TYPES_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngluniformstate.c b/gsk/ngl/gskngluniformstate.c
deleted file mode 100644 (file)
index 9a70088..0000000
+++ /dev/null
@@ -1,269 +0,0 @@
-/* gskngluniformstate.c
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#include "config.h"
-
-#include <gsk/gskroundedrectprivate.h>
-#include <string.h>
-
-#include "gskngluniformstateprivate.h"
-
-static const guint8 uniform_sizes[] = {
-  0,
-
-  sizeof (Uniform1f),
-  sizeof (Uniform2f),
-  sizeof (Uniform3f),
-  sizeof (Uniform4f),
-
-  sizeof (Uniform1f),
-  sizeof (Uniform2f),
-  sizeof (Uniform3f),
-  sizeof (Uniform4f),
-
-  sizeof (Uniform1i),
-  sizeof (Uniform2i),
-  sizeof (Uniform3i),
-  sizeof (Uniform4i),
-
-  sizeof (Uniform1ui),
-
-  sizeof (guint),
-
-  sizeof (graphene_matrix_t),
-  sizeof (GskRoundedRect),
-  sizeof (GdkRGBA),
-
-  0,
-};
-
-GskNglUniformState *
-gsk_ngl_uniform_state_new (void)
-{
-  GskNglUniformState *state;
-
-  state = g_atomic_rc_box_new0 (GskNglUniformState);
-  state->programs = g_hash_table_new_full (NULL, NULL, NULL, g_free);
-  state->values_len = 4096;
-  state->values_pos = 0;
-  state->values_buf = g_malloc (4096);
-
-  memset (state->apply_hash, 0, sizeof state->apply_hash);
-
-  return g_steal_pointer (&state);
-}
-
-GskNglUniformState *
-gsk_ngl_uniform_state_ref (GskNglUniformState *state)
-{
-  return g_atomic_rc_box_acquire (state);
-}
-
-static void
-gsk_ngl_uniform_state_finalize (gpointer data)
-{
-  GskNglUniformState *state = data;
-
-  g_clear_pointer (&state->programs, g_hash_table_unref);
-  g_clear_pointer (&state->values_buf, g_free);
-}
-
-void
-gsk_ngl_uniform_state_unref (GskNglUniformState *state)
-{
-  g_atomic_rc_box_release_full (state, gsk_ngl_uniform_state_finalize);
-}
-
-gpointer
-gsk_ngl_uniform_state_init_value (GskNglUniformState    *state,
-                                  GskNglUniformProgram  *program,
-                                  GskNglUniformFormat    format,
-                                  guint                  array_count,
-                                  guint                  key,
-                                  GskNglUniformMapping **infoptr)
-{
-  GskNglUniformMapping *mapping;
-  guint offset;
-
-  g_assert (state != NULL);
-  g_assert (array_count < 32);
-  g_assert ((int)format >= 0 && format < GSK_NGL_UNIFORM_FORMAT_LAST);
-  g_assert (format > 0);
-  g_assert (program != NULL);
-  g_assert (key < program->n_mappings);
-
-  mapping = &program->mappings[key];
-
-  if (mapping->location == -1)
-    {
-      *infoptr = NULL;
-      return NULL;
-    }
-
-  if G_LIKELY (format == mapping->info.format)
-    {
-      if G_LIKELY (array_count <= mapping->info.array_count)
-        {
-          *infoptr = mapping;
-          return GSK_NGL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
-        }
-
-      /* We found the uniform, but there is not enough space for the
-       * amount that was requested. Instead, allocate new space and
-       * set the value to "initial" so that the caller just writes
-       * over the previous value.
-       *
-       * This can happen when using dynamic array lengths like the
-       * "n_color_stops" in gradient shaders.
-       */
-      goto setup_info;
-    }
-  else if (mapping->info.format == 0)
-    {
-      goto setup_info;
-    }
-  else
-    {
-      g_critical ("Attempt to access uniform with different type of value "
-                  "than it was initialized with. Program %u Location %u. "
-                  "Was %d now %d (array length %d now %d).",
-                  program->program_id, key, mapping->info.format, format,
-                  mapping->info.array_count, array_count);
-      *infoptr = NULL;
-      return NULL;
-    }
-
-setup_info:
-
-  gsk_ngl_uniform_state_realloc (state,
-                                 uniform_sizes[format] * MAX (1, array_count),
-                                 &offset);
-
-  /* we have 21 bits for offset */
-  g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS));
-
-  mapping->info.format = format;
-  mapping->info.offset = offset;
-  mapping->info.array_count = array_count;
-  mapping->info.initial = TRUE;
-  mapping->stamp = 0;
-
-  *infoptr = mapping;
-
-  return GSK_NGL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
-}
-
-void
-gsk_ngl_uniform_state_end_frame (GskNglUniformState *state)
-{
-  GHashTableIter iter;
-  GskNglUniformProgram *program;
-  guint allocator = 0;
-
-  g_return_if_fail (state != NULL);
-
-  /* After a frame finishes, we want to remove all our copies of uniform
-   * data that isn't needed any longer. Since we treat it as uninitialized
-   * after this frame (to reset it on first use next frame) we can just
-   * discard it but keep an allocation around to reuse.
-   */
-
-  g_hash_table_iter_init (&iter, state->programs);
-  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&program))
-    {
-      for (guint j = 0; j < program->n_mappings; j++)
-        {
-          GskNglUniformMapping *mapping = &program->mappings[j];
-          guint size;
-
-          /* Skip unused uniform mappings */
-          if (mapping->info.format == 0 || mapping->location == -1)
-            continue;
-
-          /* Calculate how much size is needed for the uniform, including arrays */
-          size = uniform_sizes[mapping->info.format] * MAX (1, mapping->info.array_count);
-
-          /* Adjust alignment for value */
-          allocator += gsk_ngl_uniform_state_align (allocator, size);
-
-          /* Offset is in slots of 4 bytes */
-          mapping->info.offset = allocator / 4;
-          mapping->info.initial = TRUE;
-          mapping->stamp = 0;
-
-          /* Now advance for this items data */
-          allocator += size;
-        }
-    }
-
-  state->values_pos = allocator;
-
-  /* It can happen that our space requirements grow due to
-   * difference in order increasing padding. As a pragmatic
-   * solution to this, just increase the allocation to cover
-   * the predefined mappins.
-   */
-  if (allocator > state->values_len)
-    {
-      while (allocator > state->values_len)
-        state->values_len *= 2;
-      state->values_buf = g_realloc (state->values_buf, state->values_len);
-    }
-
-  memset (state->apply_hash, 0, sizeof state->apply_hash);
-}
-
-gsize
-gsk_ngl_uniform_format_size (GskNglUniformFormat format)
-{
-  g_assert (format > 0);
-  g_assert (format < GSK_NGL_UNIFORM_FORMAT_LAST);
-
-  return uniform_sizes[format];
-}
-
-GskNglUniformProgram *
-gsk_ngl_uniform_state_get_program (GskNglUniformState         *state,
-                                   guint                       program,
-                                   const GskNglUniformMapping *mappings,
-                                   guint                       n_mappings)
-{
-  GskNglUniformProgram *ret;
-
-  g_return_val_if_fail (state != NULL, NULL);
-  g_return_val_if_fail (program > 0, NULL);
-  g_return_val_if_fail (program < G_MAXUINT, NULL);
-  g_return_val_if_fail (n_mappings <= G_N_ELEMENTS (ret->mappings), NULL);
-
-  ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program));
-
-  if (ret == NULL)
-    {
-      ret = g_new0 (GskNglUniformProgram, 1);
-      ret->program_id = program;
-      ret->n_mappings = n_mappings;
-
-      memcpy (ret->mappings, mappings, n_mappings * sizeof *mappings);
-
-      g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret);
-    }
-
-  return ret;
-}
diff --git a/gsk/ngl/gskngluniformstateprivate.h b/gsk/ngl/gskngluniformstateprivate.h
deleted file mode 100644 (file)
index 835498b..0000000
+++ /dev/null
@@ -1,836 +0,0 @@
-/* gskngluniformstateprivate.h
- *
- * Copyright 2020 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef GSK_NGL_UNIFORM_STATE_PRIVATE_H
-#define GSK_NGL_UNIFORM_STATE_PRIVATE_H
-
-#include "gskngltypesprivate.h"
-
-G_BEGIN_DECLS
-
-typedef struct { float v0; } Uniform1f;
-typedef struct { float v0; float v1; } Uniform2f;
-typedef struct { float v0; float v1; float v2; } Uniform3f;
-typedef struct { float v0; float v1; float v2; float v3; } Uniform4f;
-
-typedef struct { int v0; } Uniform1i;
-typedef struct { int v0; int v1; } Uniform2i;
-typedef struct { int v0; int v1; int v2; } Uniform3i;
-typedef struct { int v0; int v1; int v2; int v3; } Uniform4i;
-
-typedef struct { guint v0; } Uniform1ui;
-
-#define GSK_NGL_UNIFORM_ARRAY_BITS  5
-#define GSK_NGL_UNIFORM_FORMAT_BITS 5
-#define GSK_NGL_UNIFORM_OFFSET_BITS 21
-
-typedef struct _GskNglUniformInfo
-{
-  guint initial     : 1;
-  guint format      : GSK_NGL_UNIFORM_FORMAT_BITS;
-  guint array_count : GSK_NGL_UNIFORM_ARRAY_BITS;
-  guint offset      : GSK_NGL_UNIFORM_OFFSET_BITS;
-} GskNglUniformInfo;
-
-G_STATIC_ASSERT (sizeof (GskNglUniformInfo) == 4);
-
-typedef struct _GskNglUniformMapping
-{
-  const char *name;
-  GskNglUniformInfo info;
-  guint stamp;
-  int location;
-} GskNglUniformMapping;
-
-typedef struct _GskNglUniformProgram
-{
-  guint program_id;
-  guint n_uniforms : 12;
-  guint has_attachments : 1;
-  guint n_mappings;
-  GskNglUniformMapping mappings[32];
-} GskNglUniformProgram;
-
-typedef struct _GskNglUniformState
-{
-  GHashTable *programs;
-  guint8 *values_buf;
-  guint values_pos;
-  guint values_len;
-  GskNglUniformInfo apply_hash[512];
-} GskNglUniformState;
-
-typedef enum _GskNglUniformKind
-{
-  GSK_NGL_UNIFORM_FORMAT_1F = 1,
-  GSK_NGL_UNIFORM_FORMAT_2F,
-  GSK_NGL_UNIFORM_FORMAT_3F,
-  GSK_NGL_UNIFORM_FORMAT_4F,
-
-  GSK_NGL_UNIFORM_FORMAT_1FV,
-  GSK_NGL_UNIFORM_FORMAT_2FV,
-  GSK_NGL_UNIFORM_FORMAT_3FV,
-  GSK_NGL_UNIFORM_FORMAT_4FV,
-
-  GSK_NGL_UNIFORM_FORMAT_1I,
-  GSK_NGL_UNIFORM_FORMAT_2I,
-  GSK_NGL_UNIFORM_FORMAT_3I,
-  GSK_NGL_UNIFORM_FORMAT_4I,
-
-  GSK_NGL_UNIFORM_FORMAT_1UI,
-
-  GSK_NGL_UNIFORM_FORMAT_TEXTURE,
-
-  GSK_NGL_UNIFORM_FORMAT_MATRIX,
-  GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT,
-  GSK_NGL_UNIFORM_FORMAT_COLOR,
-
-  GSK_NGL_UNIFORM_FORMAT_LAST
-} GskNglUniformFormat;
-
-G_STATIC_ASSERT (GSK_NGL_UNIFORM_FORMAT_LAST < (1 << GSK_NGL_UNIFORM_FORMAT_BITS));
-
-GskNglUniformState   *gsk_ngl_uniform_state_new         (void);
-GskNglUniformState   *gsk_ngl_uniform_state_ref         (GskNglUniformState          *state);
-void                  gsk_ngl_uniform_state_unref       (GskNglUniformState          *state);
-GskNglUniformProgram *gsk_ngl_uniform_state_get_program (GskNglUniformState          *state,
-                                                         guint                        program,
-                                                         const GskNglUniformMapping  *mappings,
-                                                         guint                        n_mappings);
-void                  gsk_ngl_uniform_state_end_frame   (GskNglUniformState          *state);
-gsize                 gsk_ngl_uniform_format_size       (GskNglUniformFormat          format);
-gpointer              gsk_ngl_uniform_state_init_value  (GskNglUniformState          *state,
-                                                         GskNglUniformProgram        *program,
-                                                         GskNglUniformFormat          format,
-                                                         guint                        array_count,
-                                                         guint                        key,
-                                                         GskNglUniformMapping       **out_mapping);
-
-#define GSK_NGL_UNIFORM_VALUE(base, offset) ((gpointer)((base) + ((offset) * 4)))
-#define gsk_ngl_uniform_state_get_uniform_data(state,offset) GSK_NGL_UNIFORM_VALUE((state)->values_buf, offset)
-
-static inline gpointer
-gsk_ngl_uniform_state_get_value (GskNglUniformState    *state,
-                                 GskNglUniformProgram  *program,
-                                 GskNglUniformFormat    format,
-                                 guint                  array_count,
-                                 guint                  key,
-                                 guint                  stamp,
-                                 GskNglUniformMapping **out_mapping)
-{
-  GskNglUniformMapping *mapping;
-
-  g_assert (key < G_N_ELEMENTS (program->mappings));
-  g_assert (key < program->n_mappings);
-
-  mapping = &program->mappings[key];
-
-  /* Short-circuit if the program optimized the uniform out */
-  if (mapping->location == -1)
-    return NULL;
-
-  /* If the stamp is the same, then we can ignore the request
-   * and short-circuit as early as possible. This requires the
-   * caller to increment their private stamp when they change
-   * internal state.
-   *
-   * This is generally used for the shared uniforms like projection,
-   * modelview, clip, etc to avoid so many comparisons which cost
-   * considerable CPU.
-   */
-  if (stamp != 0 && stamp == mapping->stamp)
-    return NULL;
-
-  if G_LIKELY (format == mapping->info.format && array_count <= mapping->info.array_count)
-    {
-      *out_mapping = mapping;
-      return GSK_NGL_UNIFORM_VALUE (state->values_buf, mapping->info.offset);
-    }
-
-  return gsk_ngl_uniform_state_init_value (state, program, format, array_count, key, out_mapping);
-}
-
-G_GNUC_PURE static inline guint
-gsk_ngl_uniform_state_align (guint current_pos,
-                             guint size)
-{
-  guint align = size > 8 ? 16 : (size > 4 ? 8 : 4);
-  guint masked = current_pos & (align - 1);
-
-  g_assert (size > 0);
-  g_assert (align == 4 || align == 8 || align == 16);
-  g_assert (masked < align);
-
-  return align - masked;
-}
-
-static inline gpointer
-gsk_ngl_uniform_state_realloc (GskNglUniformState *state,
-                               guint               size,
-                               guint              *offset)
-{
-  guint padding = gsk_ngl_uniform_state_align (state->values_pos, size);
-
-  if G_UNLIKELY (state->values_len - padding - size < state->values_pos)
-    {
-      state->values_len *= 2;
-      state->values_buf = g_realloc (state->values_buf, state->values_len);
-    }
-
-  /* offsets are in slots of 4 to use fewer bits */
-  g_assert ((state->values_pos + padding) % 4 == 0);
-  *offset = (state->values_pos + padding) / 4;
-  state->values_pos += padding + size;
-
-  return GSK_NGL_UNIFORM_VALUE (state->values_buf, *offset);
-}
-
-#define GSK_NGL_UNIFORM_STATE_REPLACE(info, u, type, count)                                \
-  G_STMT_START {                                                                           \
-    if ((info)->info.initial && count == (info)->info.array_count)                         \
-      {                                                                                    \
-        u = GSK_NGL_UNIFORM_VALUE (state->values_buf, (info)->info.offset);                \
-      }                                                                                    \
-    else                                                                                   \
-      {                                                                                    \
-        guint offset;                                                                      \
-        u = gsk_ngl_uniform_state_realloc (state, sizeof(type) * MAX (1, count), &offset); \
-        g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS));                            \
-        (info)->info.offset = offset;                                                      \
-        /* We might have increased array length */                                         \
-        (info)->info.array_count = count;                                                  \
-      }                                                                                    \
-  } G_STMT_END
-
-static inline void
-gsk_ngl_uniform_info_changed (GskNglUniformMapping *info,
-                              guint                     stamp)
-{
-  info->stamp = stamp;
-  info->info.initial = FALSE;
-}
-
-static inline void
-gsk_ngl_uniform_state_set1f (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             float                 value0)
-{
-  Uniform1f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != 0);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1F, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f , 1);
-          u->v0 = value0;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set2f (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             float                 value0,
-                             float                 value1)
-{
-  Uniform2f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2F, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set3f (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             float                 value0,
-                             float                 value1,
-                             float                 value2)
-{
-  Uniform3f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3F, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          u->v2 = value2;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set4f (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             float                 value0,
-                             float                 value1,
-                             float                 value2,
-                             float                 value3)
-{
-  Uniform4f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4F, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          u->v2 = value2;
-          u->v3 = value3;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set1ui (GskNglUniformState   *state,
-                              GskNglUniformProgram *program,
-                              guint                 key,
-                              guint                 stamp,
-                              guint                 value0)
-{
-  Uniform1ui *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1UI, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1ui, 1);
-          u->v0 = value0;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set1i (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             int                   value0)
-{
-  Uniform1i *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1I, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1i, 1);
-          u->v0 = value0;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set2i (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             int                   value0,
-                             int                   value1)
-{
-  Uniform2i *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2I, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2i, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set3i (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             int                   value0,
-                             int                   value1,
-                             int                   value2)
-{
-  Uniform3i *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3I, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3i, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          u->v2 = value2;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set4i (GskNglUniformState   *state,
-                             GskNglUniformProgram *program,
-                             guint                 key,
-                             guint                 stamp,
-                             int                   value0,
-                             int                   value1,
-                             int                   value2,
-                             int                   value3)
-{
-  Uniform4i *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4I, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4i, 1);
-          u->v0 = value0;
-          u->v1 = value1;
-          u->v2 = value2;
-          u->v3 = value3;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set_rounded_rect (GskNglUniformState   *state,
-                                        GskNglUniformProgram *program,
-                                        guint                 key,
-                                        guint                 stamp,
-                                        const GskRoundedRect *rounded_rect)
-{
-  GskRoundedRect *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (rounded_rect != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || memcmp (u, rounded_rect, sizeof *u) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GskRoundedRect, 1);
-          memcpy (u, rounded_rect, sizeof *rounded_rect);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set_matrix (GskNglUniformState      *state,
-                                  GskNglUniformProgram    *program,
-                                  guint                    key,
-                                  guint                    stamp,
-                                  const graphene_matrix_t *matrix)
-{
-  graphene_matrix_t *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (matrix != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_MATRIX, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || memcmp (u, matrix, sizeof *u) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, graphene_matrix_t, 1);
-          memcpy (u, matrix, sizeof *matrix);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-/**
- * gsk_ngl_uniform_state_set_texture:
- * @state: a `GskNglUniformState`
- * @program: the program id
- * @location: the location of the texture
- * @texture_slot: a texturing slot such as GL_TEXTURE0
- *
- * Sets the uniform expecting a texture to @texture_slot. This API
- * expects a texture slot such as GL_TEXTURE0 to reduce chances of
- * miss-use by the caller.
- *
- * The value stored to the uniform is in the form of 0 for GL_TEXTURE0,
- * 1 for GL_TEXTURE1, and so on.
- */
-static inline void
-gsk_ngl_uniform_state_set_texture (GskNglUniformState   *state,
-                                   GskNglUniformProgram *program,
-                                   guint                 key,
-                                   guint                 stamp,
-                                   guint                 texture_slot)
-{
-  GskNglUniformMapping *info;
-  guint *u;
-
-  g_assert (texture_slot >= GL_TEXTURE0);
-  g_assert (texture_slot < GL_TEXTURE16);
-
-  texture_slot -= GL_TEXTURE0;
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_TEXTURE, 1, key, stamp, &info)))
-    {
-      if (info->info.initial || *u != texture_slot)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, guint, 1);
-          *u = texture_slot;
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-/**
- * gsk_ngl_uniform_state_set_color:
- * @state: a `GskNglUniformState`
- * @program: a program id > 0
- * @location: the uniform location
- * @color: a color to set or %NULL for transparent
- *
- * Sets a uniform to the color described by @color. This is a convenience
- * function to allow callers to avoid having to translate colors to floats
- * in other portions of the renderer.
- */
-static inline void
-gsk_ngl_uniform_state_set_color (GskNglUniformState   *state,
-                                 GskNglUniformProgram *program,
-                                 guint                 key,
-                                 guint                 stamp,
-                                 const GdkRGBA        *color)
-{
-  static const GdkRGBA transparent = {0};
-  GskNglUniformMapping *info;
-  GdkRGBA *u;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_COLOR, 1, key, stamp, &info)))
-    {
-      if (color == NULL)
-        color = &transparent;
-
-      if (info->info.initial || memcmp (color, u, sizeof *u) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GdkRGBA, 1);
-          memcpy (u, color, sizeof *color);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set1fv (GskNglUniformState   *state,
-                              GskNglUniformProgram *program,
-                              guint                 key,
-                              guint                 stamp,
-                              guint                 count,
-                              const float          *value)
-{
-  Uniform1f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (count > 0);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1FV, count, key, stamp, &info)))
-    {
-      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f, count);
-          memcpy (u, value, sizeof (Uniform1f) * count);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set2fv (GskNglUniformState   *state,
-                              GskNglUniformProgram *program,
-                              guint                 key,
-                              guint                 stamp,
-                              guint                 count,
-                              const float          *value)
-{
-  Uniform2f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (count > 0);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2FV, count, key, stamp, &info)))
-    {
-      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, count);
-          memcpy (u, value, sizeof (Uniform2f) * count);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set3fv (GskNglUniformState   *state,
-                              GskNglUniformProgram *program,
-                              guint                 key,
-                              guint                 stamp,
-                              guint                 count,
-                              const float          *value)
-{
-  Uniform3f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (count > 0);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3FV, count, key, stamp, &info)))
-    {
-      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, count);
-          memcpy (u, value, sizeof (Uniform3f) * count);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline void
-gsk_ngl_uniform_state_set4fv (GskNglUniformState   *state,
-                              GskNglUniformProgram *program,
-                              guint                 key,
-                              guint                 stamp,
-                              guint                 count,
-                              const float          *value)
-{
-  Uniform4f *u;
-  GskNglUniformMapping *info;
-
-  g_assert (state != NULL);
-  g_assert (program != NULL);
-  g_assert (count > 0);
-
-  if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4FV, count, key, stamp, &info)))
-    {
-      if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0)
-        {
-          GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, count);
-          memcpy (u, value, sizeof (Uniform4f) * count);
-          gsk_ngl_uniform_info_changed (info, stamp);
-        }
-    }
-}
-
-static inline guint
-gsk_ngl_uniform_state_fmix (guint program,
-                            guint location)
-{
-  guint h = (program << 16) | location;
-
-   h ^= h >> 16;
-   h *= 0x85ebca6b;
-   h ^= h >> 13;
-   h *= 0xc2b2ae35;
-   h ^= h >> 16;
-
-   return h;
-}
-
-/*
- * gsk_ngl_uniform_state_apply:
- * @state: the uniform state
- * @program: the program id
- * @location: the location of the uniform
- * @offset: the offset of the data within the buffer
- * @info: the uniform info
- *
- * This function can be used to apply state that was previously recorded
- * by the `GskNglUniformState`.
- *
- * It is specifically useful from the `GskNglCommandQueue` to execute uniform
- * changes but only when they have changed from the current value.
- */
-static inline void
-gsk_ngl_uniform_state_apply (GskNglUniformState *state,
-                             guint               program,
-                             guint               location,
-                             GskNglUniformInfo   info)
-{
-  guint index = gsk_ngl_uniform_state_fmix (program, location) % G_N_ELEMENTS (state->apply_hash);
-  gconstpointer dataptr = GSK_NGL_UNIFORM_VALUE (state->values_buf, info.offset);
-
-  /* aligned, can treat as unsigned */
-  if (*(guint *)&info == *(guint *)&state->apply_hash[index])
-    return;
-
-  state->apply_hash[index] = info;
-
-  /* TODO: We could do additional comparisons here to make sure we are
-   *       changing state.
-   */
-
-  switch (info.format)
-    {
-    case GSK_NGL_UNIFORM_FORMAT_1F:
-      glUniform1fv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_2F:
-      glUniform2fv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_3F:
-      glUniform3fv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_4F:
-      glUniform4fv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_1FV:
-      glUniform1fv (location, info.array_count, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_2FV:
-      glUniform2fv (location, info.array_count, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_3FV:
-      glUniform3fv (location, info.array_count, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_4FV:
-      glUniform4fv (location, info.array_count, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_1I:
-    case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
-      glUniform1iv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_2I:
-      glUniform2iv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_3I:
-      glUniform3iv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_4I:
-      glUniform4iv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_1UI:
-      glUniform1uiv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_MATRIX: {
-      float mat[16];
-      graphene_matrix_to_float (dataptr, mat);
-      glUniformMatrix4fv (location, 1, GL_FALSE, mat);
-#if 0
-      /* TODO: If Graphene can give us a peek here on platforms
-       * where the format is float[16] (most/all x86_64?) then
-       * We can avoid the SIMD operation to convert the format.
-       */
-      G_STATIC_ASSERT (sizeof (graphene_matrix_t) == 16*4);
-      glUniformMatrix4fv (location, 1, GL_FALSE, dataptr);
-#endif
-    }
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_COLOR:
-      glUniform4fv (location, 1, dataptr);
-    break;
-
-    case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT:
-      glUniform4fv (location, 3, dataptr);
-    break;
-
-    default:
-      g_assert_not_reached ();
-    }
-}
-
-G_END_DECLS
-
-#endif /* GSK_NGL_UNIFORM_STATE_PRIVATE_H */
diff --git a/gsk/ngl/inlinearray.h b/gsk/ngl/inlinearray.h
deleted file mode 100644 (file)
index 98a9255..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-#ifndef __INLINE_ARRAY_H__
-#define __INLINE_ARRAY_H__
-
-#define DEFINE_INLINE_ARRAY(Type, prefix, ElementType)              \
-  typedef struct _##Type {                                          \
-    gsize len;                                                      \
-    gsize allocated;                                                \
-    ElementType *items;                                             \
-  } Type;                                                           \
-                                                                    \
-  static inline void                                                \
-  prefix##_init (Type  *ar,                                         \
-                 gsize  initial_size)                               \
-  {                                                                 \
-    ar->len = 0;                                                    \
-    ar->allocated = initial_size ? initial_size : 16;               \
-    ar->items = g_new0 (ElementType, ar->allocated);                \
-  }                                                                 \
-                                                                    \
-  static inline void                                                \
-  prefix##_clear (Type *ar)                                         \
-  {                                                                 \
-    ar->len = 0;                                                    \
-    ar->allocated = 0;                                              \
-    g_clear_pointer (&ar->items, g_free);                           \
-  }                                                                 \
-                                                                    \
-  static inline ElementType *                                       \
-  prefix##_head (Type *ar)                                          \
-  {                                                                 \
-    return &ar->items[0];                                           \
-  }                                                                 \
-                                                                    \
-  static inline ElementType *                                       \
-  prefix##_tail (Type *ar)                                          \
-  {                                                                 \
-    return &ar->items[ar->len-1];                                   \
-  }                                                                 \
-                                                                    \
-  static inline ElementType *                                       \
-  prefix##_append (Type *ar)                                        \
-  {                                                                 \
-    if G_UNLIKELY (ar->len == ar->allocated)                        \
-      {                                                             \
-        ar->allocated *= 2;                                         \
-        ar->items = g_renew (ElementType, ar->items, ar->allocated);\
-      }                                                             \
-                                                                    \
-    ar->len++;                                                      \
-                                                                    \
-    return prefix##_tail (ar);                                      \
-  }                                                                 \
-                                                                    \
-  static inline ElementType *                                       \
-  prefix##_append_n (Type  *ar,                                     \
-                     gsize  n)                                      \
-  {                                                                 \
-    if G_UNLIKELY ((ar->len + n) > ar->allocated)                   \
-      {                                                             \
-        while ((ar->len + n) > ar->allocated)                       \
-          ar->allocated *= 2;                                       \
-        ar->items = g_renew (ElementType, ar->items, ar->allocated);\
-      }                                                             \
-                                                                    \
-    ar->len += n;                                                   \
-                                                                    \
-    return &ar->items[ar->len-n];                                   \
-  }                                                                 \
-                                                                    \
-  static inline gsize                                               \
-  prefix##_index_of (Type              *ar,                         \
-                     const ElementType *element)                    \
-  {                                                                 \
-    return element - &ar->items[0];                                 \
-  }
-
-#endif /* __INLINE_ARRAY_H__ */
diff --git a/gsk/ngl/ninesliceprivate.h b/gsk/ngl/ninesliceprivate.h
deleted file mode 100644 (file)
index b2b787b..0000000
+++ /dev/null
@@ -1,310 +0,0 @@
-/* ninesliceprivate.h
- *
- * Copyright 2017 Timm Bäder <mail@baedert.org>
- * Copyright 2021 Christian Hergert <chergert@redhat.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#ifndef __NINE_SLICE_PRIVATE_H__
-#define __NINE_SLICE_PRIVATE_H__
-
-#include "gskngltextureprivate.h"
-
-#if 0
-# define DEBUG_NINE_SLICE
-#endif
-
-G_BEGIN_DECLS
-
-enum {
-  NINE_SLICE_TOP_LEFT      = 0,
-  NINE_SLICE_TOP_CENTER    = 1,
-  NINE_SLICE_TOP_RIGHT     = 2,
-  NINE_SLICE_LEFT_CENTER   = 3,
-  NINE_SLICE_CENTER        = 4,
-  NINE_SLICE_RIGHT_CENTER  = 5,
-  NINE_SLICE_BOTTOM_LEFT   = 6,
-  NINE_SLICE_BOTTOM_CENTER = 7,
-  NINE_SLICE_BOTTOM_RIGHT  = 8,
-};
-
-static inline bool G_GNUC_PURE
-nine_slice_is_visible (const GskNglTextureNineSlice *slice)
-{
-  return slice->rect.width > 0 && slice->rect.height > 0;
-}
-
-static inline void
-nine_slice_rounded_rect (GskNglTextureNineSlice *slices,
-                         const GskRoundedRect   *rect)
-{
-  const graphene_point_t *origin = &rect->bounds.origin;
-  const graphene_size_t *size = &rect->bounds.size;
-  int top_height = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].height,
-                               rect->corner[GSK_CORNER_TOP_RIGHT].height));
-  int bottom_height = ceilf (MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
-                                  rect->corner[GSK_CORNER_BOTTOM_RIGHT].height));
-  int right_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width,
-                                rect->corner[GSK_CORNER_BOTTOM_RIGHT].width));
-  int left_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].width,
-                               rect->corner[GSK_CORNER_BOTTOM_LEFT].width));
-
-  /* Top left */
-  slices[0].rect.x = origin->x;
-  slices[0].rect.y = origin->y;
-  slices[0].rect.width = left_width;
-  slices[0].rect.height = top_height;
-
-  /* Top center */
-  slices[1].rect.x = origin->x + size->width / 2.0 - 0.5;
-  slices[1].rect.y = origin->y;
-  slices[1].rect.width = 1;
-  slices[1].rect.height = top_height;
-
-  /* Top right */
-  slices[2].rect.x = origin->x + size->width - right_width;
-  slices[2].rect.y = origin->y;
-  slices[2].rect.width = right_width;
-  slices[2].rect.height = top_height;
-
-  /* Left center */
-  slices[3].rect.x = origin->x;
-  slices[3].rect.y = origin->y + size->height / 2;
-  slices[3].rect.width = left_width;
-  slices[3].rect.height = 1;
-
-  /* center */
-  slices[4].rect.x = origin->x + size->width / 2.0 - 0.5;
-  slices[4].rect.y = origin->y + size->height / 2.0 - 0.5;
-  slices[4].rect.width = 1;
-  slices[4].rect.height = 1;
-
-  /* Right center */
-  slices[5].rect.x = origin->x + size->width - right_width;
-  slices[5].rect.y = origin->y + (size->height / 2.0) - 0.5;
-  slices[5].rect.width = right_width;
-  slices[5].rect.height = 1;
-
-  /* Bottom Left */
-  slices[6].rect.x = origin->x;
-  slices[6].rect.y = origin->y + size->height - bottom_height;
-  slices[6].rect.width = left_width;
-  slices[6].rect.height = bottom_height;
-
-  /* Bottom center */
-  slices[7].rect.x = origin->x + (size->width / 2.0) - 0.5;
-  slices[7].rect.y = origin->y + size->height - bottom_height;
-  slices[7].rect.width = 1;
-  slices[7].rect.height = bottom_height;
-
-  /* Bottom right */
-  slices[8].rect.x = origin->x + size->width - right_width;
-  slices[8].rect.y = origin->y + size->height - bottom_height;
-  slices[8].rect.width = right_width;
-  slices[8].rect.height = bottom_height;
-
-#ifdef DEBUG_NINE_SLICE
-  /* These only hold true when the values from ceilf() above
-   * are greater than one. Otherwise they fail, like will happen
-   * with the node editor viewing the textures zoomed out.
-   */
-  if (size->width > 1)
-    g_assert_cmpfloat (size->width, >=, left_width + right_width);
-  if (size->height > 1)
-  g_assert_cmpfloat (size->height, >=, top_height + bottom_height);
-#endif
-}
-
-static inline void
-nine_slice_to_texture_coords (GskNglTextureNineSlice *slices,
-                              int                     texture_width,
-                              int                     texture_height)
-{
-  float fw = texture_width;
-  float fh = texture_height;
-
-  for (guint i = 0; i < 9; i++)
-    {
-      GskNglTextureNineSlice *slice = &slices[i];
-
-      slice->area.x = slice->rect.x / fw;
-      slice->area.y = 1.0 - ((slice->rect.y + slice->rect.height) / fh);
-      slice->area.x2 = ((slice->rect.x + slice->rect.width) / fw);
-      slice->area.y2 = (1.0 - (slice->rect.y / fh));
-
-#ifdef DEBUG_NINE_SLICE
-      g_assert_cmpfloat (slice->area.x, >=, 0);
-      g_assert_cmpfloat (slice->area.x, <=, 1);
-      g_assert_cmpfloat (slice->area.y, >=, 0);
-      g_assert_cmpfloat (slice->area.y, <=, 1);
-      g_assert_cmpfloat (slice->area.x2, >, slice->area.x);
-      g_assert_cmpfloat (slice->area.y2, >, slice->area.y);
-#endif
-    }
-}
-
-static inline void
-nine_slice_grow (GskNglTextureNineSlice *slices,
-                 int                     amount_x,
-                 int                     amount_y)
-{
-  if (amount_x == 0 && amount_y == 0)
-    return;
-
-  /* top left */
-  slices[0].rect.x -= amount_x;
-  slices[0].rect.y -= amount_y;
-  if (amount_x > slices[0].rect.width)
-    slices[0].rect.width += amount_x * 2;
-  else
-    slices[0].rect.width += amount_x;
-
-  if (amount_y > slices[0].rect.height)
-    slices[0].rect.height += amount_y * 2;
-  else
-    slices[0].rect.height += amount_y;
-
-
-  /* Top center */
-  slices[1].rect.y -= amount_y;
-  if (amount_y > slices[1].rect.height)
-    slices[1].rect.height += amount_y * 2;
-  else
-    slices[1].rect.height += amount_y;
-
-  /* top right */
-  slices[2].rect.y -= amount_y;
-  if (amount_x > slices[2].rect.width)
-    {
-      slices[2].rect.x -= amount_x;
-      slices[2].rect.width += amount_x * 2;
-    }
-  else
-    {
-     slices[2].rect.width += amount_x;
-    }
-
-  if (amount_y > slices[2].rect.height)
-    slices[2].rect.height += amount_y * 2;
-  else
-    slices[2].rect.height += amount_y;
-
-
-
-  slices[3].rect.x -= amount_x;
-  if (amount_x > slices[3].rect.width)
-    slices[3].rect.width += amount_x * 2;
-  else
-    slices[3].rect.width += amount_x;
-
-  /* Leave center alone */
-
-  if (amount_x > slices[5].rect.width)
-    {
-      slices[5].rect.x -= amount_x;
-      slices[5].rect.width += amount_x * 2;
-    }
-  else
-    {
-      slices[5].rect.width += amount_x;
-    }
-
-
-  /* Bottom left */
-  slices[6].rect.x -= amount_x;
-  if (amount_x > slices[6].rect.width)
-    {
-      slices[6].rect.width += amount_x * 2;
-    }
-  else
-    {
-      slices[6].rect.width += amount_x;
-    }
-
-  if (amount_y > slices[6].rect.height)
-    {
-      slices[6].rect.y -= amount_y;
-      slices[6].rect.height += amount_y * 2;
-    }
-  else
-    {
-      slices[6].rect.height += amount_y;
-    }
-
-
-  /* Bottom center */
-  if (amount_y > slices[7].rect.height)
-    {
-      slices[7].rect.y -= amount_y;
-      slices[7].rect.height += amount_y * 2;
-    }
-  else
-    {
-      slices[7].rect.height += amount_y;
-    }
-
-  if (amount_x > slices[8].rect.width)
-    {
-      slices[8].rect.x -= amount_x;
-      slices[8].rect.width += amount_x * 2;
-    }
-  else
-    {
-      slices[8].rect.width += amount_x;
-    }
-
-  if (amount_y > slices[8].rect.height)
-    {
-      slices[8].rect.y -= amount_y;
-      slices[8].rect.height += amount_y * 2;
-    }
-  else
-    {
-      slices[8].rect.height += amount_y;
-    }
-
-#ifdef DEBUG_NINE_SLICE
-  /* These cannot be relied on in all cases right now, specifically
-   * when viewing data zoomed out.
-   */
-  for (guint i = 0; i < 9; i ++)
-    {
-      g_assert_cmpint (slices[i].rect.x, >=, 0);
-      g_assert_cmpint (slices[i].rect.y, >=, 0);
-      g_assert_cmpint (slices[i].rect.width, >=, 0);
-      g_assert_cmpint (slices[i].rect.height, >=, 0);
-    }
-
-  /* Rows don't overlap */
-  for (guint i = 0; i < 3; i++)
-    {
-      int lhs = slices[i * 3 + 0].rect.x + slices[i * 3 + 0].rect.width;
-      int rhs = slices[i * 3 + 1].rect.x;
-
-      /* Ignore the case where we are scaled out and the
-       * positioning is degenerate, such as from node-editor.
-       */
-      if (rhs > 1)
-        g_assert_cmpint (lhs, <, rhs);
-    }
-#endif
-
-}
-
-G_END_DECLS
-
-#endif /* __NINE_SLICE_PRIVATE_H__ */
diff --git a/gsk/ngl/resources/blend.glsl b/gsk/ngl/resources/blend.glsl
deleted file mode 100644 (file)
index 609f9f2..0000000
+++ /dev/null
@@ -1,314 +0,0 @@
-// VERTEX_SHADER:
-// blend.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// blend.glsl
-
-uniform int u_mode;
-uniform sampler2D u_source2;
-
-float
-combine (float source, float backdrop)
-{
-  return source + backdrop * (1.0 - source);
-}
-
-vec4
-composite (vec4 Cs, vec4 Cb, vec3 B)
-{
-  float ao = Cs.a + Cb.a * (1.0 - Cs.a);
-  vec3 Co = (Cs.a*(1.0 - Cb.a)*Cs.rgb + Cs.a*Cb.a*B + (1.0 - Cs.a)*Cb.a*Cb.rgb) / ao;
-  return vec4(Co, ao);
-}
-
-vec4
-normal (vec4 Cs, vec4 Cb)
-{
-  return composite (Cs, Cb, Cs.rgb);
-}
-
-vec4
-multiply (vec4 Cs, vec4 Cb)
-{
-  return composite (Cs, Cb, Cs.rgb * Cb.rgb);
-}
-
-vec4
-difference (vec4 Cs, vec4 Cb)
-{
-  return composite (Cs, Cb, abs(Cs.rgb - Cb.rgb));
-}
-
-vec4
-screen (vec4 Cs, vec4 Cb)
-{
-  return composite (Cs, Cb, Cs.rgb + Cb.rgb - Cs.rgb * Cb.rgb);
-}
-
-float
-hard_light (float source, float backdrop)
-{
-  if (source <= 0.5)
-    return 2.0 * backdrop * source;
-  else
-    return 2.0 * (backdrop + source - backdrop * source) - 1.0;
-}
-
-vec4
-hard_light (vec4 Cs, vec4 Cb)
-{
-  vec3 B = vec3 (hard_light (Cs.r, Cb.r),
-                 hard_light (Cs.g, Cb.g),
-                 hard_light (Cs.b, Cb.b));
-  return composite (Cs, Cb, B);
-}
-
-float
-soft_light (float source, float backdrop)
-{
-  float db;
-
-  if (backdrop <= 0.25)
-    db = ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop;
-  else
-    db = sqrt (backdrop);
-
-  if (source <= 0.5)
-    return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop);
-  else
-    return backdrop + (2.0 * source - 1.0) * (db - backdrop);
-}
-
-vec4
-soft_light (vec4 Cs, vec4 Cb)
-{
-  vec3 B = vec3 (soft_light (Cs.r, Cb.r),
-                 soft_light (Cs.g, Cb.g),
-                 soft_light (Cs.b, Cb.b));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-overlay (vec4 Cs, vec4 Cb)
-{
-  vec3 B = vec3 (hard_light (Cb.r, Cs.r),
-                 hard_light (Cb.g, Cs.g),
-                 hard_light (Cb.b, Cs.b));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-darken (vec4 Cs, vec4 Cb)
-{
-  vec3 B = min (Cs.rgb, Cb.rgb);
-  return composite (Cs, Cb, B);
-}
-
-vec4
-lighten (vec4 Cs, vec4 Cb)
-{
-  vec3 B = max (Cs.rgb, Cb.rgb);
-  return composite (Cs, Cb, B);
-}
-
-float
-color_dodge (float source, float backdrop)
-{
-  return (source == 1.0) ? source : min (backdrop / (1.0 - source), 1.0);
-}
-
-vec4
-color_dodge (vec4 Cs, vec4 Cb)
-{
-  vec3 B = vec3 (color_dodge (Cs.r, Cb.r),
-                 color_dodge (Cs.g, Cb.g),
-                 color_dodge (Cs.b, Cb.b));
-  return composite (Cs, Cb, B);
-}
-
-
-float
-color_burn (float source, float backdrop)
-{
-  return (source == 0.0) ? source : max ((1.0 - ((1.0 - backdrop) / source)), 0.0);
-}
-
-vec4
-color_burn (vec4 Cs, vec4 Cb)
-{
-  vec3 B = vec3 (color_burn (Cs.r, Cb.r),
-                 color_burn (Cs.g, Cb.g),
-                 color_burn (Cs.b, Cb.b));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-exclusion (vec4 Cs, vec4 Cb)
-{
-  vec3 B = Cb.rgb + Cs.rgb - 2.0 * Cb.rgb * Cs.rgb;
-  return composite (Cs, Cb, B);
-}
-
-float
-lum (vec3 c)
-{
-  return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;
-}
-
-vec3
-clip_color (vec3 c)
-{
-  float l = lum (c);
-  float n = min (c.r, min (c.g, c.b));
-  float x = max (c.r, max (c.g, c.b));
-  if (n < 0.0) c = l + (((c - l) * l) / (l - n));
-  if (x > 1.0) c = l + (((c - l) * (1.0 - l)) / (x - l));
-  return c;
-}
-
-vec3
-set_lum (vec3 c, float l)
-{
-  float d = l - lum (c);
-  return clip_color (vec3 (c.r + d, c.g + d, c.b + d));
-}
-
-float
-sat (vec3 c)
-{
-  return max (c.r, max (c.g, c.b)) - min (c.r, min (c.g, c.b));
-}
-
-vec3
-set_sat (vec3 c, float s)
-{
-  float cmin = min (c.r, min (c.g, c.b));
-  float cmax = max (c.r, max (c.g, c.b));
-  vec3 res;
-
-  if (cmax == cmin)
-    res = vec3 (0, 0, 0);
-  else
-    {
-      if (c.r == cmax)
-        {
-          if (c.g == cmin)
-            {
-              res.b = ((c.b - cmin) * s) / (cmax - cmin);
-              res.g = 0.0;
-            }
-          else
-            {
-              res.g = ((c.g - cmin) * s) / (cmax - cmin);
-              res.b = 0.0;
-            }
-          res.r = s;
-        }
-      else if (c.g == cmax)
-        {
-          if (c.r == cmin)
-            {
-              res.b = ((c.b - cmin) * s) / (cmax - cmin);
-              res.r = 0.0;
-            }
-          else
-            {
-              res.r = ((c.r - cmin) * s) / (cmax - cmin);
-              res.b = 0.0;
-            }
-          res.g = s;
-        }
-      else
-        {
-          if (c.r == cmin)
-            {
-              res.g = ((c.g - cmin) * s) / (cmax - cmin);
-              res.r = 0.0;
-            }
-          else
-            {
-              res.r = ((c.r - cmin) * s) / (cmax - cmin);
-              res.g = 0.0;
-            }
-          res.b = s;
-        }
-    }
-  return res;
-}
-
-vec4
-color (vec4 Cs, vec4 Cb)
-{
-  vec3 B = set_lum (Cs.rgb, lum (Cb.rgb));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-hue (vec4 Cs, vec4 Cb)
-{
-  vec3 B = set_lum (set_sat (Cs.rgb, sat (Cb.rgb)), lum (Cb.rgb));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-saturation (vec4 Cs, vec4 Cb)
-{
-  vec3 B = set_lum (set_sat (Cb.rgb, sat (Cs.rgb)), lum (Cb.rgb));
-  return composite (Cs, Cb, B);
-}
-
-vec4
-luminosity (vec4 Cs, vec4 Cb)
-{
-  vec3 B = set_lum (Cb.rgb, lum (Cs.rgb));
-  return composite (Cs, Cb, B);
-}
-
-void main() {
-  vec4 bottom_color = GskTexture(u_source, vUv);
-  vec4 top_color = GskTexture(u_source2, vUv);
-
-  vec4 result;
-  if (u_mode == 0)
-    result = normal(top_color, bottom_color);
-  else if (u_mode == 1)
-    result = multiply(top_color, bottom_color);
-  else if (u_mode == 2)
-    result = screen(top_color, bottom_color);
-  else if (u_mode == 3)
-    result = overlay(top_color, bottom_color);
-  else if (u_mode == 4)
-    result = darken(top_color, bottom_color);
-  else if (u_mode == 5)
-    result = lighten(top_color, bottom_color);
-  else if (u_mode == 6)
-    result = color_dodge(top_color, bottom_color);
-  else if (u_mode == 7)
-    result = color_burn(top_color, bottom_color);
-  else if (u_mode == 8)
-    result = hard_light(top_color, bottom_color);
-  else if (u_mode == 9)
-    result = soft_light(top_color, bottom_color);
-  else if (u_mode == 10)
-    result = difference(top_color, bottom_color);
-  else if (u_mode == 11)
-    result = exclusion(top_color, bottom_color);
-  else if (u_mode == 12)
-    result = color(top_color, bottom_color);
-  else if (u_mode == 13)
-    result = hue(top_color, bottom_color);
-  else if (u_mode == 14)
-    result = saturation(top_color, bottom_color);
-  else if (u_mode == 15)
-    result = luminosity(top_color, bottom_color);
-  else
-    discard;
-
-  gskSetScaledOutputColor(result, u_alpha);
-}
diff --git a/gsk/ngl/resources/blit.glsl b/gsk/ngl/resources/blit.glsl
deleted file mode 100644 (file)
index ced047b..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-// VERTEX_SHADER:
-// blit.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// blit.glsl
-
-void main() {
-  vec4 diffuse = GskTexture(u_source, vUv);
-
-  gskSetScaledOutputColor(diffuse, u_alpha);
-}
diff --git a/gsk/ngl/resources/blur.glsl b/gsk/ngl/resources/blur.glsl
deleted file mode 100644 (file)
index 7e7c1c2..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-// VERTEX_SHADER:
-// blur.glsl
-
-uniform float u_blur_radius;
-uniform vec2 u_blur_size;
-uniform vec2 u_blur_dir;
-
-_OUT_ vec2 pixel_step;
-_OUT_ float pixels_per_side;
-_OUT_ vec3 initial_gaussian;
-
-const float PI = 3.14159265;
-const float RADIUS_MULTIPLIER = 2.0;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-
-  pixel_step = (vec2(1.0) / u_blur_size) * u_blur_dir;
-  pixels_per_side = floor(u_blur_radius * RADIUS_MULTIPLIER / 2.0);
-
-  float sigma = u_blur_radius / 2.0; // *shrug*
-  initial_gaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
-  initial_gaussian.y = exp(-0.5 / (sigma * sigma));
-  initial_gaussian.z = initial_gaussian.y * initial_gaussian.y;
-}
-
-// FRAGMENT_SHADER:
-// blur.glsl
-
-_IN_ vec2 pixel_step;
-_IN_ float pixels_per_side;
-_IN_ vec3 initial_gaussian;
-
-// blur_radius 0 is NOT supported and MUST be caught before.
-
-// Partially from http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html
-void main() {
-  vec3 incrementalGaussian = initial_gaussian;
-
-  float coefficientSum = 0.0;
-  vec4 sum = GskTexture(u_source, vUv) * incrementalGaussian.x;
-  coefficientSum += incrementalGaussian.x;
-  incrementalGaussian.xy *= incrementalGaussian.yz;
-
-  vec2 p = pixel_step;
-  for (int i = 1; i <= int(pixels_per_side); i++) {
-    sum += GskTexture(u_source, vUv - p) * incrementalGaussian.x;
-    sum += GskTexture(u_source, vUv + p) * incrementalGaussian.x;
-
-    coefficientSum += 2.0 * incrementalGaussian.x;
-    incrementalGaussian.xy *= incrementalGaussian.yz;
-
-    p += pixel_step;
-  }
-
-  gskSetOutputColor(sum / coefficientSum);
-}
diff --git a/gsk/ngl/resources/border.glsl b/gsk/ngl/resources/border.glsl
deleted file mode 100644 (file)
index b8653ba..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-// VERTEX_SHADER:
-// border.glsl
-
-uniform vec4 u_widths;
-uniform vec4[3] u_outline_rect;
-
-_OUT_ vec4 final_color;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  final_color = gsk_scaled_premultiply(aColor, u_alpha);
-
-  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
-  GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths);
-
-  gsk_rounded_rect_transform(outside, u_modelview);
-  gsk_rounded_rect_transform(inside, u_modelview);
-
-  gsk_rounded_rect_encode(outside, transformed_outside_outline);
-  gsk_rounded_rect_encode(inside, transformed_inside_outline);
-}
-
-// FRAGMENT_SHADER:
-// border.glsl
-
-uniform vec4[3] u_outline_rect;
-
-_IN_ vec4 final_color;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  vec2 frag = gsk_get_frag_coord();
-
-  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
-                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
-                      0.0, 1.0);
-
-  gskSetScaledOutputColor(final_color, alpha);
-}
diff --git a/gsk/ngl/resources/color.glsl b/gsk/ngl/resources/color.glsl
deleted file mode 100644 (file)
index ba98b1b..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-// VERTEX_SHADER:
-// color.glsl
-
-_OUT_ vec4 final_color;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  final_color = gsk_scaled_premultiply(aColor, u_alpha);
-}
-
-// FRAGMENT_SHADER:
-// color.glsl
-
-_IN_ vec4 final_color;
-
-void main() {
-  gskSetOutputColor(final_color);
-}
-
diff --git a/gsk/ngl/resources/color_matrix.glsl b/gsk/ngl/resources/color_matrix.glsl
deleted file mode 100644 (file)
index 51835f0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-// VERTEX_SHADER:
-// color_matrix.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// color_matrix.glsl
-
-uniform mat4 u_color_matrix;
-uniform vec4 u_color_offset;
-
-void main() {
-  vec4 color = GskTexture(u_source, vUv);
-
-  // Un-premultilpy
-  if (color.a != 0.0)
-    color.rgb /= color.a;
-
-  color = u_color_matrix * color + u_color_offset;
-  color = clamp(color, 0.0, 1.0);
-
-  gskSetOutputColor(gsk_scaled_premultiply(color, u_alpha));
-}
diff --git a/gsk/ngl/resources/coloring.glsl b/gsk/ngl/resources/coloring.glsl
deleted file mode 100644 (file)
index 007c54d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// VERTEX_SHADER:
-// coloring.glsl
-
-_OUT_ vec4 final_color;
-_OUT_ float use_color;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-
-  // We use this shader for both plain glyphs (used as mask)
-  // and color glpyhs (used as source). The renderer sets
-  // aColor to vec4(-1) for color glyhs.
-  if (distance(aColor,vec4(-1)) < 0.1)
-    use_color = 0.0;
-  else
-    use_color = 1.0;
-
-  final_color = gsk_scaled_premultiply(aColor, u_alpha);
-}
-
-// FRAGMENT_SHADER:
-// coloring.glsl
-
-_IN_ vec4 final_color;
-_IN_ float use_color;
-
-void main() {
-  vec4 diffuse = GskTexture(u_source, vUv);
-
-  gskSetOutputColor(mix(diffuse * u_alpha, final_color * diffuse.a, use_color));
-}
diff --git a/gsk/ngl/resources/conic_gradient.glsl b/gsk/ngl/resources/conic_gradient.glsl
deleted file mode 100644 (file)
index 3df33bc..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-// VERTEX_SHADER
-// conic_gradient.glsl
-
-uniform vec4 u_geometry;
-
-_NOPERSPECTIVE_ _OUT_ vec2 coord;
-
-void main() {
-  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
-
-  vec2 mv0 = u_modelview[0].xy;
-  vec2 mv1 = u_modelview[1].xy;
-  vec2 offset = aPosition - u_geometry.xy;
-
-  coord = vec2(dot(mv0, offset), dot(mv1, offset));
-}
-
-// FRAGMENT_SHADER:
-// conic_gradient.glsl
-
-#define MAX_COLOR_STOPS 6
-
-#ifdef GSK_LEGACY
-uniform int u_num_color_stops;
-#else
-uniform highp int u_num_color_stops; // Why? Because it works like this.
-#endif
-
-uniform vec4 u_geometry;
-uniform float u_color_stops[MAX_COLOR_STOPS * 5];
-
-_NOPERSPECTIVE_ _IN_ vec2 coord;
-
-float get_offset(int index) {
-  // u_color_stops[5 * index] makes Intel Windows driver crash.
-  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
-  int base = 5 * index;
-  return u_color_stops[base];
-}
-
-vec4 get_color(int index) {
-  int base = 5 * index + 1;
-
-  return vec4(u_color_stops[base],
-              u_color_stops[base + 1],
-              u_color_stops[base + 2],
-              u_color_stops[base + 3]);
-}
-
-void main() {
-  // direction of point in range [-PI, PI]
-  vec2 pos = floor(coord);
-  float angle = atan(pos.y, pos.x);
-
-  // fract() does the modulo here, so now we have progress
-  // into the current conic
-  float offset = fract(angle * u_geometry.z + u_geometry.w);
-  float curr_offset;
-  float next_offset;
-
-  next_offset = get_offset(0);
-  if (offset < next_offset) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
-    return;
-  }
-
-  if (offset >= get_offset(u_num_color_stops - 1)) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
-    return;
-  }
-
-  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
-    curr_offset = next_offset;
-    next_offset = get_offset(i + 1);
-
-    if (offset < next_offset) {
-      float f = (offset - curr_offset) / (next_offset - curr_offset);
-      vec4 curr_color = gsk_premultiply(get_color(i));
-      vec4 next_color = gsk_premultiply(get_color(i + 1));
-      vec4 color = mix(curr_color, next_color, f);
-      gskSetScaledOutputColor(color, u_alpha);
-      return;
-    }
-  }
-}
diff --git a/gsk/ngl/resources/cross_fade.glsl b/gsk/ngl/resources/cross_fade.glsl
deleted file mode 100644 (file)
index e61af59..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-// VERTEX_SHADER:
-// cross_fade.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// cross_fade.glsl
-
-uniform float u_progress;
-uniform sampler2D u_source2;
-
-void main() {
-  vec4 source1 = GskTexture(u_source, vUv);  // start child
-  vec4 source2 = GskTexture(u_source2, vUv); // end child
-
-  float p_start = (1.0 - u_progress) * u_alpha;
-  float p_end = u_progress * u_alpha;
-  vec4 color = (p_start * source1) + (p_end * source2);
-  gskSetOutputColor(color);
-}
diff --git a/gsk/ngl/resources/custom.glsl b/gsk/ngl/resources/custom.glsl
deleted file mode 100644 (file)
index 6f91df6..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-// VERTEX_SHADER:
-// custom.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// custom.glsl
-
-// The shader supplies:
-void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv);
-
-uniform vec2 u_size;
-uniform sampler2D u_source2;
-uniform sampler2D u_source3;
-uniform sampler2D u_source4;
-
-void main() {
-  vec4 fragColor;
-  vec2 fragCoord = vec2(vUv.x * u_size.x, (1.0-vUv.y) * u_size.y);
-  mainImage(fragColor, fragCoord, u_size, vUv);
-  gskSetOutputColor(fragColor);
-}
diff --git a/gsk/ngl/resources/filled_border.glsl b/gsk/ngl/resources/filled_border.glsl
deleted file mode 100644 (file)
index d503dd0..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-// VERTEX_SHADER:
-// filled_border.glsl
-
-uniform vec4 u_widths;
-uniform vec4[3] u_outline_rect;
-
-_OUT_ vec4 outer_color;
-_OUT_ vec4 inner_color;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  outer_color = gsk_scaled_premultiply(aColor, u_alpha);
-  inner_color = gsk_scaled_premultiply(aColor2, u_alpha);
-
-  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
-  GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths);
-
-  gsk_rounded_rect_transform(outside, u_modelview);
-  gsk_rounded_rect_transform(inside, u_modelview);
-
-  gsk_rounded_rect_encode(outside, transformed_outside_outline);
-  gsk_rounded_rect_encode(inside, transformed_inside_outline);
-}
-
-// FRAGMENT_SHADER:
-// filled_border.glsl
-
-uniform vec4[3] u_outline_rect;
-
-_IN_ vec4 outer_color;
-_IN_ vec4 inner_color;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  vec2 frag = gsk_get_frag_coord();
-  float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag);
-  float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag);
-
-  float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0);
-  float alpha2 = clamp(inner_coverage, 0.0, 1.0);
-
-  gskSetOutputColor((outer_color * alpha) + (inner_color * alpha2));
-}
diff --git a/gsk/ngl/resources/inset_shadow.glsl b/gsk/ngl/resources/inset_shadow.glsl
deleted file mode 100644 (file)
index f052a08..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-// VERTEX_SHADER:
-// inset_shadow.glsl
-
-uniform float u_spread;
-uniform vec2 u_offset;
-uniform vec4[3] u_outline_rect;
-
-_OUT_ vec4 final_color;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  final_color = gsk_scaled_premultiply(aColor, u_alpha);
-
-  GskRoundedRect outside = gsk_create_rect(u_outline_rect);
-  GskRoundedRect inside = gsk_rounded_rect_shrink(outside, vec4(u_spread));
-
-  gsk_rounded_rect_offset(inside, u_offset);
-
-  gsk_rounded_rect_transform(outside, u_modelview);
-  gsk_rounded_rect_transform(inside, u_modelview);
-
-  gsk_rounded_rect_encode(outside, transformed_outside_outline);
-  gsk_rounded_rect_encode(inside, transformed_inside_outline);
-}
-
-// FRAGMENT_SHADER:
-// inset_shadow.glsl
-
-_IN_ vec4 final_color;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  vec2 frag = gsk_get_frag_coord();
-
-  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
-                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
-                      0.0, 1.0);
-
-  gskSetScaledOutputColor(final_color, alpha);
-}
diff --git a/gsk/ngl/resources/linear_gradient.glsl b/gsk/ngl/resources/linear_gradient.glsl
deleted file mode 100644 (file)
index 153d7af..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-// VERTEX_SHADER
-// linear_gradient.glsl
-uniform vec4 u_points;
-
-_NOPERSPECTIVE_ _OUT_ vec4 info;
-
-void main() {
-  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
-
-  vec2 mv0 = u_modelview[0].xy;
-  vec2 mv1 = u_modelview[1].xy;
-  vec2 offset = aPosition - u_points.xy;
-  vec2 coord = vec2(dot(mv0, offset),
-                    dot(mv1, offset));
-
-  // Original equation:
-  // VS | maxDist = length(end - start);
-  // VS | gradient = end - start;
-  // VS | gradientLength = length(gradient);
-  // FS | pos = frag_coord - start
-  // FS | proj = (dot(gradient, pos) / (gradientLength * gradientLength)) * gradient
-  // FS | offset = length(proj) / maxDist
-
-  // Simplified formula derivation:
-  // 1. Notice that maxDist = gradientLength:
-  // offset = length(proj) / gradientLength
-  // 2. Let gnorm = gradient / gradientLength, then:
-  // proj = (dot(gnorm * gradientLength, pos) / (gradientLength * gradientLength)) * (gnorm * gradientLength) =
-  //      = dot(gnorm, pos) * gnorm
-  // 3. Since gnorm is unit length then:
-  // length(proj) = length(dot(gnorm, pos) * gnorm) = dot(gnorm, pos)
-  // 4. We can avoid the FS division by passing a scaled pos from the VS:
-  // offset = dot(gnorm, pos) / gradientLength = dot(gnorm, pos / gradientLength)
-  // 5. 1.0 / length(gradient) is inversesqrt(dot(gradient, gradient)) in GLSL
-  vec2 gradient = vec2(dot(mv0, u_points.zw),
-                       dot(mv1, u_points.zw));
-  float rcp_gradient_length = inversesqrt(dot(gradient, gradient));
-
-  info = rcp_gradient_length * vec4(coord, gradient);
-}
-
-// FRAGMENT_SHADER:
-// linear_gradient.glsl
-
-#define MAX_COLOR_STOPS 6
-
-#ifdef GSK_LEGACY
-uniform int u_num_color_stops;
-#else
-uniform highp int u_num_color_stops; // Why? Because it works like this.
-#endif
-
-uniform float u_color_stops[MAX_COLOR_STOPS * 5];
-uniform bool u_repeat;
-
-_NOPERSPECTIVE_ _IN_ vec4 info;
-
-float get_offset(int index) {
-  // u_color_stops[5 * index] makes Intel Windows driver crash.
-  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
-  int base = 5 * index;
-  return u_color_stops[base];
-}
-
-vec4 get_color(int index) {
-  int base = 5 * index + 1;
-
-  return vec4(u_color_stops[base],
-              u_color_stops[base + 1],
-              u_color_stops[base + 2],
-              u_color_stops[base + 3]);
-}
-
-void main() {
-  float offset = dot(info.xy, info.zw);
-  float curr_offset;
-  float next_offset;
-
-  if (u_repeat) {
-    offset = fract(offset);
-  }
-
-  next_offset = get_offset(0);
-  if (offset < next_offset) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
-    return;
-  }
-
-  if (offset >= get_offset(u_num_color_stops - 1)) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
-    return;
-  }
-
-  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
-    curr_offset = next_offset;
-    next_offset = get_offset(i + 1);
-
-    if (offset < next_offset) {
-      float f = (offset - curr_offset) / (next_offset - curr_offset);
-      vec4 curr_color = gsk_premultiply(get_color(i));
-      vec4 next_color = gsk_premultiply(get_color(i + 1));
-      vec4 color = mix(curr_color, next_color, f);
-      gskSetScaledOutputColor(color, u_alpha);
-      return;
-    }
-  }
-}
diff --git a/gsk/ngl/resources/outset_shadow.glsl b/gsk/ngl/resources/outset_shadow.glsl
deleted file mode 100644 (file)
index 44b05aa..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-// VERTEX_SHADER:
-// outset_shadow.glsl
-
-uniform vec4[3] u_outline_rect;
-
-_OUT_ vec4 final_color;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outline;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-
-  final_color = gsk_scaled_premultiply(aColor, u_alpha);
-
-  GskRoundedRect outline = gsk_create_rect(u_outline_rect);
-  gsk_rounded_rect_transform(outline, u_modelview);
-  gsk_rounded_rect_encode(outline, transformed_outline);
-}
-
-// FRAGMENT_SHADER:
-// outset_shadow.glsl
-
-_IN_ vec4 final_color;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outline;
-
-void main() {
-  vec2 frag = gsk_get_frag_coord();
-  float alpha = GskTexture(u_source, vUv).a;
-
-  alpha *= (1.0 -  clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outline), frag), 0.0, 1.0));
-
-  gskSetScaledOutputColor(final_color, alpha);
-}
diff --git a/gsk/ngl/resources/preamble.fs.glsl b/gsk/ngl/resources/preamble.fs.glsl
deleted file mode 100644 (file)
index c1c5a95..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-uniform sampler2D u_source;
-uniform mat4 u_projection;
-uniform mat4 u_modelview;
-uniform float u_alpha;
-uniform vec4 u_viewport;
-uniform vec4[3] u_clip_rect;
-
-#if defined(GSK_LEGACY)
-_OUT_ vec4 outputColor;
-#elif !defined(GSK_GLES)
-_OUT_ vec4 outputColor;
-#endif
-
-_IN_ vec2 vUv;
-
-
-GskRoundedRect gsk_decode_rect(_GSK_ROUNDED_RECT_UNIFORM_ r)
-{
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-  return GskRoundedRect(r[0], r[1], r[2]);
-#else
-  return r;
-#endif
-}
-
-float
-gsk_ellipsis_dist (vec2 p, vec2 radius)
-{
-  if (radius == vec2(0, 0))
-    return 0.0;
-
-  vec2 p0 = p / radius;
-  vec2 p1 = 2.0 * p0 / radius;
-
-  return (dot(p0, p0) - 1.0) / length (p1);
-}
-
-float
-gsk_ellipsis_coverage (vec2 point, vec2 center, vec2 radius)
-{
-  float d = gsk_ellipsis_dist (point - center, radius);
-  return clamp (0.5 - d, 0.0, 1.0);
-}
-
-float
-gsk_rounded_rect_coverage (GskRoundedRect r, vec2 p)
-{
-  if (p.x < r.bounds.x || p.y < r.bounds.y ||
-      p.x >= r.bounds.z || p.y >= r.bounds.w)
-    return 0.0;
-
-  vec2 ref_tl = r.corner_points1.xy;
-  vec2 ref_tr = r.corner_points1.zw;
-  vec2 ref_br = r.corner_points2.xy;
-  vec2 ref_bl = r.corner_points2.zw;
-
-  if (p.x >= ref_tl.x && p.x >= ref_bl.x &&
-      p.x <= ref_tr.x && p.x <= ref_br.x)
-    return 1.0;
-
-  if (p.y >= ref_tl.y && p.y >= ref_tr.y &&
-      p.y <= ref_bl.y && p.y <= ref_br.y)
-    return 1.0;
-
-  vec2 rad_tl = r.corner_points1.xy - r.bounds.xy;
-  vec2 rad_tr = r.corner_points1.zw - r.bounds.zy;
-  vec2 rad_br = r.corner_points2.xy - r.bounds.zw;
-  vec2 rad_bl = r.corner_points2.zw - r.bounds.xw;
-
-  float d_tl = gsk_ellipsis_coverage(p, ref_tl, rad_tl);
-  float d_tr = gsk_ellipsis_coverage(p, ref_tr, rad_tr);
-  float d_br = gsk_ellipsis_coverage(p, ref_br, rad_br);
-  float d_bl = gsk_ellipsis_coverage(p, ref_bl, rad_bl);
-
-  vec4 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl);
-
-  bvec4 is_out = bvec4(p.x < ref_tl.x && p.y < ref_tl.y,
-                       p.x > ref_tr.x && p.y < ref_tr.y,
-                       p.x > ref_br.x && p.y > ref_br.y,
-                       p.x < ref_bl.x && p.y > ref_bl.y);
-
-  return 1.0 - dot(vec4(is_out), corner_coverages);
-}
-
-float
-gsk_rect_coverage (vec4 r, vec2 p)
-{
-  if (p.x < r.x || p.y < r.y ||
-      p.x >= r.z || p.y >= r.w)
-    return 0.0;
-
-  return 1.0;
-}
-
-vec4 GskTexture(sampler2D sampler, vec2 texCoords) {
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-  return texture2D(sampler, texCoords);
-#else
-  return texture(sampler, texCoords);
-#endif
-}
-
-#ifdef GSK_GL3
-layout(origin_upper_left) in vec4 gl_FragCoord;
-#endif
-
-vec2 gsk_get_frag_coord() {
-  vec2 fc = gl_FragCoord.xy;
-
-#ifdef GSK_GL3
-  fc += u_viewport.xy;
-#else
-  fc.x += u_viewport.x;
-  fc.y = (u_viewport.y + u_viewport.w) - fc.y;
-#endif
-
-  return fc;
-}
-
-void gskSetOutputColor(vec4 color) {
-  vec4 result;
-
-#if defined(NO_CLIP)
-  result = color;
-#elif defined(RECT_CLIP)
-  float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect),
-                                     gsk_get_frag_coord());
-  result = color * coverage;
-#else
-  float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect),
-                                             gsk_get_frag_coord());
-  result = color * coverage;
-#endif
-
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-  gl_FragColor = result;
-#else
-  outputColor = result;
-#endif
-}
-
-void gskSetScaledOutputColor(vec4 color, float alpha) {
-  vec4 result;
-
-#if defined(NO_CLIP)
-  result = color * alpha;
-#elif defined(RECT_CLIP)
-  float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect),
-                                     gsk_get_frag_coord());
-  result = color * (alpha * coverage);
-#else
-  float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect),
-                                             gsk_get_frag_coord());
-  result = color * (alpha * coverage);
-#endif
-
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-  gl_FragColor = result;
-#else
-  outputColor = result;
-#endif
-}
diff --git a/gsk/ngl/resources/preamble.glsl b/gsk/ngl/resources/preamble.glsl
deleted file mode 100644 (file)
index 8bc007b..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef GSK_LEGACY
-precision highp float;
-#endif
-
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-#define _OUT_ varying
-#define _IN_ varying
-#define _NOPERSPECTIVE_
-#define _GSK_ROUNDED_RECT_UNIFORM_ vec4[3]
-#else
-#define _OUT_ out
-#define _IN_ in
-#define _NOPERSPECTIVE_ noperspective
-#define _GSK_ROUNDED_RECT_UNIFORM_ GskRoundedRect
-#endif
-
-
-struct GskRoundedRect
-{
-  vec4 bounds; // Top left and bottom right
-  // Look, arrays can't be in structs if you want to return the struct
-  // from a function in gles or whatever. Just kill me.
-  vec4 corner_points1; // xy = top left, zw = top right
-  vec4 corner_points2; // xy = bottom right, zw = bottom left
-};
-
-// Transform from a C GskRoundedRect to what we need.
-GskRoundedRect
-gsk_create_rect(vec4[3] data)
-{
-  vec4 bounds = vec4(data[0].xy, data[0].xy + data[0].zw);
-
-  vec4 corner_points1 = vec4(bounds.xy + data[1].xy,
-                             bounds.zy + vec2(data[1].zw * vec2(-1, 1)));
-  vec4 corner_points2 = vec4(bounds.zw + (data[2].xy * vec2(-1, -1)),
-                             bounds.xw + vec2(data[2].zw * vec2(1, -1)));
-
-  return GskRoundedRect(bounds, corner_points1, corner_points2);
-}
-
-vec4
-gsk_get_bounds(vec4[3] data)
-{
-  return vec4(data[0].xy, data[0].xy + data[0].zw);
-}
-
-vec4 gsk_premultiply(vec4 c) {
-  return vec4(c.rgb * c.a, c.a);
-}
-
-vec4 gsk_scaled_premultiply(vec4 c, float s) {
-  // Fast version of gsk_premultiply(c) * s
-  // 4 muls instead of 7
-  float a = s * c.a;
-
-  return vec4(c.rgb * a, a);
-}
diff --git a/gsk/ngl/resources/preamble.vs.glsl b/gsk/ngl/resources/preamble.vs.glsl
deleted file mode 100644 (file)
index 758bca8..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-uniform mat4 u_projection;
-uniform mat4 u_modelview;
-uniform float u_alpha;
-
-#if defined(GSK_GLES) || defined(GSK_LEGACY)
-attribute vec2 aPosition;
-attribute vec2 aUv;
-attribute vec4 aColor;
-attribute vec4 aColor2;
-_OUT_ vec2 vUv;
-#else
-_IN_ vec2 aPosition;
-_IN_ vec2 aUv;
-_IN_ vec4 aColor;
-_IN_ vec4 aColor2;
-_OUT_ vec2 vUv;
-#endif
-
-// amount is: top, right, bottom, left
-GskRoundedRect
-gsk_rounded_rect_shrink (GskRoundedRect r, vec4 amount)
-{
-  vec4 new_bounds = r.bounds + vec4(1.0,1.0,-1.0,-1.0) * amount.wxyz;
-  vec4 new_corner_points1 = r.corner_points1;
-  vec4 new_corner_points2 = r.corner_points2;
-
-  if (r.corner_points1.xy == r.bounds.xy) new_corner_points1.xy = new_bounds.xy;
-  if (r.corner_points1.zw == r.bounds.zy) new_corner_points1.zw = new_bounds.zy;
-  if (r.corner_points2.xy == r.bounds.zw) new_corner_points2.xy = new_bounds.zw;
-  if (r.corner_points2.zw == r.bounds.xw) new_corner_points2.zw = new_bounds.xw;
-
-  return GskRoundedRect (new_bounds, new_corner_points1, new_corner_points2);
-}
-
-void
-gsk_rounded_rect_offset(inout GskRoundedRect r, vec2 offset)
-{
-  r.bounds.xy += offset;
-  r.bounds.zw += offset;
-  r.corner_points1.xy += offset;
-  r.corner_points1.zw += offset;
-  r.corner_points2.xy += offset;
-  r.corner_points2.zw += offset;
-}
-
-void gsk_rounded_rect_transform(inout GskRoundedRect r, mat4 mat)
-{
-  r.bounds.xy = (mat * vec4(r.bounds.xy, 0.0, 1.0)).xy;
-  r.bounds.zw = (mat * vec4(r.bounds.zw, 0.0, 1.0)).xy;
-
-  r.corner_points1.xy = (mat * vec4(r.corner_points1.xy, 0.0, 1.0)).xy;
-  r.corner_points1.zw = (mat * vec4(r.corner_points1.zw, 0.0, 1.0)).xy;
-
-  r.corner_points2.xy = (mat * vec4(r.corner_points2.xy, 0.0, 1.0)).xy;
-  r.corner_points2.zw = (mat * vec4(r.corner_points2.zw, 0.0, 1.0)).xy;
-}
-
-#if defined(GSK_LEGACY)
-// Can't have out or inout array parameters...
-#define gsk_rounded_rect_encode(r, uni) uni[0] = r.bounds; uni[1] = r.corner_points1; uni[2] = r.corner_points2;
-#else
-void gsk_rounded_rect_encode(GskRoundedRect r, out _GSK_ROUNDED_RECT_UNIFORM_ out_r)
-{
-#if defined(GSK_GLES)
-  out_r[0] = r.bounds;
-  out_r[1] = r.corner_points1;
-  out_r[2] = r.corner_points2;
-#else
-  out_r = r;
-#endif
-}
-
-#endif
diff --git a/gsk/ngl/resources/radial_gradient.glsl b/gsk/ngl/resources/radial_gradient.glsl
deleted file mode 100644 (file)
index 4311fc5..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// VERTEX_SHADER
-// radial_gradient.glsl
-
-uniform vec4 u_geometry;
-
-_NOPERSPECTIVE_ _OUT_ vec2 coord;
-
-void main() {
-  gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0));
-
-  vec2 mv0 = u_modelview[0].xy;
-  vec2 mv1 = u_modelview[1].xy;
-  vec2 offset = aPosition - u_geometry.xy;
-  vec2 dir = vec2(dot(mv0, offset),
-                  dot(mv1, offset));
-
-  coord = dir * u_geometry.zw;
-}
-
-// FRAGMENT_SHADER:
-// radial_gradient.glsl
-
-#define MAX_COLOR_STOPS 6
-
-#ifdef GSK_LEGACY
-uniform int u_num_color_stops;
-#else
-uniform highp int u_num_color_stops;
-#endif
-
-uniform bool u_repeat;
-uniform vec2 u_range;
-uniform float u_color_stops[MAX_COLOR_STOPS * 5];
-
-_NOPERSPECTIVE_ _IN_ vec2 coord;
-
-float get_offset(int index) {
-  // u_color_stops[5 * index] makes Intel Windows driver crash.
-  // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783
-  int base = 5 * index;
-  return u_color_stops[base];
-}
-
-vec4 get_color(int index) {
-  int base = 5 * index + 1;
-
-  return vec4(u_color_stops[base],
-              u_color_stops[base + 1],
-              u_color_stops[base + 2],
-              u_color_stops[base + 3]);
-}
-
-void main() {
-  // Reverse scale
-  float offset = length(coord) * u_range.x + u_range.y;
-  float curr_offset;
-  float next_offset;
-
-  if (u_repeat) {
-    offset = fract(offset);
-  }
-
-  next_offset = get_offset(0);
-  if (offset < next_offset) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha));
-    return;
-  }
-
-  if (offset >= get_offset(u_num_color_stops - 1)) {
-    gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha));
-    return;
-  }
-
-  for (int i = 0; i < MAX_COLOR_STOPS; i++) {
-    curr_offset = next_offset;
-    next_offset = get_offset(i + 1);
-
-    if (offset < next_offset) {
-      float f = (offset - curr_offset) / (next_offset - curr_offset);
-      vec4 curr_color = gsk_premultiply(get_color(i));
-      vec4 next_color = gsk_premultiply(get_color(i + 1));
-      vec4 color = mix(curr_color, next_color, f);
-      gskSetScaledOutputColor(color, u_alpha);
-      return;
-    }
-  }
-}
diff --git a/gsk/ngl/resources/repeat.glsl b/gsk/ngl/resources/repeat.glsl
deleted file mode 100644 (file)
index 1b811df..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-// VERTEX_SHADER:
-// repeat.glsl
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  vUv = vec2(aUv.x, aUv.y);
-}
-
-// FRAGMENT_SHADER:
-// repeat.glsl
-
-uniform vec4 u_child_bounds;
-uniform vec4 u_texture_rect;
-
-float wrap(float f, float wrap_for) {
-  return mod(f, wrap_for);
-}
-
-/* We get the texture coordinates via vUv,
- * but that might be on a texture atlas, so we need to do the
- * wrapping ourselves.
- */
-void main() {
-
-  /* We map the texture coordinate to [1;0], then wrap it and scale the result again */
-
-  float tw = u_texture_rect.z - u_texture_rect.x;
-  float th = u_texture_rect.w - u_texture_rect.y;
-
-  float mapped_x = (vUv.x - u_texture_rect.x) / tw;
-  float mapped_y = (vUv.y - u_texture_rect.y) / th;
-
-  float wrapped_x = wrap(u_child_bounds.x + mapped_x * u_child_bounds.z, 1.0);
-  float wrapped_y = wrap(u_child_bounds.y + mapped_y * u_child_bounds.w, 1.0);
-
-  vec2 tp;
-  tp.x = u_texture_rect.x + (wrapped_x * tw);
-  tp.y = u_texture_rect.y + (wrapped_y * th);
-
-  vec4 diffuse = GskTexture(u_source, tp);
-
-  gskSetScaledOutputColor(diffuse, u_alpha);
-}
diff --git a/gsk/ngl/resources/unblurred_outset_shadow.glsl b/gsk/ngl/resources/unblurred_outset_shadow.glsl
deleted file mode 100644 (file)
index cd44212..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-// VERTEX_SHADER:
-// unblurred_outset_shadow.glsl
-
-uniform float u_spread;
-uniform vec2 u_offset;
-uniform vec4[3] u_outline_rect;
-
-_OUT_ vec4 final_color;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_OUT_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0);
-
-  final_color = gsk_premultiply(aColor) * u_alpha;
-
-  GskRoundedRect inside = gsk_create_rect(u_outline_rect);
-  GskRoundedRect outside = gsk_rounded_rect_shrink(inside, vec4(- u_spread));
-
-  gsk_rounded_rect_offset(outside, u_offset);
-
-  gsk_rounded_rect_transform(outside, u_modelview);
-  gsk_rounded_rect_transform(inside, u_modelview);
-
-  gsk_rounded_rect_encode(outside, transformed_outside_outline);
-  gsk_rounded_rect_encode(inside, transformed_inside_outline);
-}
-
-// FRAGMENT_SHADER:
-// unblurred_outset_shadow.glsl
-_IN_ vec4 final_color;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_outside_outline;
-_IN_ _GSK_ROUNDED_RECT_UNIFORM_ transformed_inside_outline;
-
-void main() {
-  vec2 frag = gsk_get_frag_coord();
-
-  float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) -
-                      gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag),
-                      0.0, 1.0);
-
-  gskSetScaledOutputColor(final_color, alpha);
-}
-
diff --git a/gsk/ngl/stb_rect_pack.c b/gsk/ngl/stb_rect_pack.c
deleted file mode 100644 (file)
index ff65846..0000000
+++ /dev/null
@@ -1,431 +0,0 @@
-
-#include "stb_rect_pack.h"
-#define STB_RECT_PACK_IMPLEMENTATION
-//////////////////////////////////////////////////////////////////////////////
-//
-//     IMPLEMENTATION SECTION
-//
-
-#ifdef STB_RECT_PACK_IMPLEMENTATION
-#ifndef STBRP_SORT
-#include <stdlib.h>
-#define STBRP_SORT qsort
-#endif
-
-#ifndef STBRP_ASSERT
-#include <assert.h>
-#define STBRP_ASSERT assert
-#endif
-
-#ifdef _MSC_VER
-#define STBRP__NOTUSED(v)  (void)(v)
-#else
-#define STBRP__NOTUSED(v)  (void)sizeof(v)
-#endif
-
-enum
-{
-   STBRP__INIT_skyline = 1
-};
-
-STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
-{
-   switch (context->init_mode) {
-      case STBRP__INIT_skyline:
-         STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
-         context->heuristic = heuristic;
-         break;
-      default:
-         STBRP_ASSERT(0);
-   }
-}
-
-STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
-{
-   if (allow_out_of_mem)
-      // if it's ok to run out of memory, then don't bother aligning them;
-      // this gives better packing, but may fail due to OOM (even though
-      // the rectangles easily fit). @TODO a smarter approach would be to only
-      // quantize once we've hit OOM, then we could get rid of this parameter.
-      context->align = 1;
-   else {
-      // if it's not ok to run out of memory, then quantize the widths
-      // so that num_nodes is always enough nodes.
-      //
-      // I.e. num_nodes * align >= width
-      //                  align >= width / num_nodes
-      //                  align = ceil(width/num_nodes)
-
-      context->align = (context->width + context->num_nodes-1) / context->num_nodes;
-   }
-}
-
-STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
-{
-   int i;
-#ifndef STBRP_LARGE_RECTS
-   STBRP_ASSERT(width <= 0xffff && height <= 0xffff);
-#endif
-
-   for (i=0; i < num_nodes-1; ++i)
-      nodes[i].next = &nodes[i+1];
-   nodes[i].next = NULL;
-   context->init_mode = STBRP__INIT_skyline;
-   context->heuristic = STBRP_HEURISTIC_Skyline_default;
-   context->free_head = &nodes[0];
-   context->active_head = &context->extra[0];
-   context->width = width;
-   context->height = height;
-   context->num_nodes = num_nodes;
-   stbrp_setup_allow_out_of_mem(context, 0);
-
-   // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
-   context->extra[0].x = 0;
-   context->extra[0].y = 0;
-   context->extra[0].next = &context->extra[1];
-   context->extra[1].x = (stbrp_coord) width;
-#ifdef STBRP_LARGE_RECTS
-   context->extra[1].y = (1<<30);
-#else
-   context->extra[1].y = 65535;
-#endif
-   context->extra[1].next = NULL;
-}
-
-// find minimum y position if it starts at x1
-static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
-{
-   stbrp_node *node = first;
-   int x1 = x0 + width;
-   int min_y, visited_width, waste_area;
-
-   STBRP__NOTUSED(c);
-
-   STBRP_ASSERT(first->x <= x0);
-
-   #if 0
-   // skip in case we're past the node
-   while (node->next->x <= x0)
-      ++node;
-   #else
-   STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
-   #endif
-
-   STBRP_ASSERT(node->x <= x0);
-
-   min_y = 0;
-   waste_area = 0;
-   visited_width = 0;
-   while (node->x < x1) {
-      if (node->y > min_y) {
-         // raise min_y higher.
-         // we've accounted for all waste up to min_y,
-         // but we'll now add more waste for everything we've visited
-         waste_area += visited_width * (node->y - min_y);
-         min_y = node->y;
-         // the first time through, visited_width might be reduced
-         if (node->x < x0)
-            visited_width += node->next->x - x0;
-         else
-            visited_width += node->next->x - node->x;
-      } else {
-         // add waste area
-         int under_width = node->next->x - node->x;
-         if (under_width + visited_width > width)
-            under_width = width - visited_width;
-         waste_area += under_width * (min_y - node->y);
-         visited_width += under_width;
-      }
-      node = node->next;
-   }
-
-   *pwaste = waste_area;
-   return min_y;
-}
-
-typedef struct
-{
-   int x,y;
-   stbrp_node **prev_link;
-} stbrp__findresult;
-
-static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
-{
-   int best_waste = (1<<30), best_x, best_y = (1 << 30);
-   stbrp__findresult fr;
-   stbrp_node **prev, *node, *tail, **best = NULL;
-
-   // align to multiple of c->align
-   width = (width + c->align - 1);
-   width -= width % c->align;
-   STBRP_ASSERT(width % c->align == 0);
-
-   node = c->active_head;
-   prev = &c->active_head;
-   while (node->x + width <= c->width) {
-      int y,waste;
-      y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
-      if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
-         // bottom left
-         if (y < best_y) {
-            best_y = y;
-            best = prev;
-         }
-      } else {
-         // best-fit
-         if (y + height <= c->height) {
-            // can only use it if it first vertically
-            if (y < best_y || (y == best_y && waste < best_waste)) {
-               best_y = y;
-               best_waste = waste;
-               best = prev;
-            }
-         }
-      }
-      prev = &node->next;
-      node = node->next;
-   }
-
-   best_x = (best == NULL) ? 0 : (*best)->x;
-
-   // if doing best-fit (BF), we also have to try aligning right edge to each node position
-   //
-   // e.g, if fitting
-   //
-   //     ____________________
-   //    |____________________|
-   //
-   //            into
-   //
-   //   |                         |
-   //   |             ____________|
-   //   |____________|
-   //
-   // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
-   //
-   // This makes BF take about 2x the time
-
-   if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
-      tail = c->active_head;
-      node = c->active_head;
-      prev = &c->active_head;
-      // find first node that's admissible
-      while (tail->x < width)
-         tail = tail->next;
-      while (tail) {
-         int xpos = tail->x - width;
-         int y,waste;
-         STBRP_ASSERT(xpos >= 0);
-         // find the left position that matches this
-         while (node->next->x <= xpos) {
-            prev = &node->next;
-            node = node->next;
-         }
-         STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
-         y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
-         if (y + height < c->height) {
-            if (y <= best_y) {
-               if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
-                  best_x = xpos;
-                  STBRP_ASSERT(y <= best_y);
-                  best_y = y;
-                  best_waste = waste;
-                  best = prev;
-               }
-            }
-         }
-         tail = tail->next;
-      }
-   }
-
-   fr.prev_link = best;
-   fr.x = best_x;
-   fr.y = best_y;
-   return fr;
-}
-
-static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
-{
-   // find best position according to heuristic
-   stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
-   stbrp_node *node, *cur;
-
-   // bail if:
-   //    1. it failed
-   //    2. the best node doesn't fit (we don't always check this)
-   //    3. we're out of memory
-   if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
-      res.prev_link = NULL;
-      return res;
-   }
-
-   // on success, create new node
-   node = context->free_head;
-   node->x = (stbrp_coord) res.x;
-   node->y = (stbrp_coord) (res.y + height);
-
-   context->free_head = node->next;
-
-   // insert the new node into the right starting point, and
-   // let 'cur' point to the remaining nodes needing to be
-   // stiched back in
-
-   cur = *res.prev_link;
-   if (cur->x < res.x) {
-      // preserve the existing one, so start testing with the next one
-      stbrp_node *next = cur->next;
-      cur->next = node;
-      cur = next;
-   } else {
-      *res.prev_link = node;
-   }
-
-   // from here, traverse cur and free the nodes, until we get to one
-   // that shouldn't be freed
-   while (cur->next && cur->next->x <= res.x + width) {
-      stbrp_node *next = cur->next;
-      // move the current node to the free list
-      cur->next = context->free_head;
-      context->free_head = cur;
-      cur = next;
-   }
-
-   // stitch the list back in
-   node->next = cur;
-
-   if (cur->x < res.x + width)
-      cur->x = (stbrp_coord) (res.x + width);
-
-#ifdef _DEBUG
-   cur = context->active_head;
-   while (cur->x < context->width) {
-      STBRP_ASSERT(cur->x < cur->next->x);
-      cur = cur->next;
-   }
-   STBRP_ASSERT(cur->next == NULL);
-
-   {
-      int count=0;
-      cur = context->active_head;
-      while (cur) {
-         cur = cur->next;
-         ++count;
-      }
-      cur = context->free_head;
-      while (cur) {
-         cur = cur->next;
-         ++count;
-      }
-      STBRP_ASSERT(count == context->num_nodes+2);
-   }
-#endif
-
-   return res;
-}
-
-static int rect_height_compare(const void *a, const void *b)
-{
-   const stbrp_rect *p = (const stbrp_rect *) a;
-   const stbrp_rect *q = (const stbrp_rect *) b;
-   if (p->h > q->h)
-      return -1;
-   if (p->h < q->h)
-      return  1;
-   return (p->w > q->w) ? -1 : (p->w < q->w);
-}
-
-static int rect_original_order(const void *a, const void *b)
-{
-   const stbrp_rect *p = (const stbrp_rect *) a;
-   const stbrp_rect *q = (const stbrp_rect *) b;
-   return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
-}
-
-#ifdef STBRP_LARGE_RECTS
-#define STBRP__MAXVAL  0xffffffff
-#else
-#define STBRP__MAXVAL  0xffff
-#endif
-
-STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
-{
-   int i, all_rects_packed = 1;
-
-   // we use the 'was_packed' field internally to allow sorting/unsorting
-   for (i=0; i < num_rects; ++i) {
-      rects[i].was_packed = i;
-   }
-
-   // sort according to heuristic
-   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
-
-   for (i=0; i < num_rects; ++i) {
-      if (rects[i].w == 0 || rects[i].h == 0) {
-         rects[i].x = rects[i].y = 0;  // empty rect needs no space
-      } else {
-         stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
-         if (fr.prev_link) {
-            rects[i].x = (stbrp_coord) fr.x;
-            rects[i].y = (stbrp_coord) fr.y;
-         } else {
-            rects[i].x = rects[i].y = STBRP__MAXVAL;
-         }
-      }
-   }
-
-   // unsort
-   STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
-
-   // set was_packed flags and all_rects_packed status
-   for (i=0; i < num_rects; ++i) {
-      rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
-      if (!rects[i].was_packed)
-         all_rects_packed = 0;
-   }
-
-   // return the all_rects_packed status
-   return all_rects_packed;
-}
-#endif
-
-/*
-------------------------------------------------------------------------------
-This software is available under 2 licenses -- choose whichever you prefer.
-------------------------------------------------------------------------------
-ALTERNATIVE A - MIT License
-Copyright (c) 2017 Sean Barrett
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-------------------------------------------------------------------------------
-ALTERNATIVE B - Public Domain (www.unlicense.org)
-This is free and unencumbered software released into the public domain.
-Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-software, either in source code form or as a compiled binary, for any purpose,
-commercial or non-commercial, and by any means.
-In jurisdictions that recognize copyright laws, the author or authors of this
-software dedicate any and all copyright interest in the software to the public
-domain. We make this dedication for the benefit of the public at large and to
-the detriment of our heirs and successors. We intend this dedication to be an
-overt act of relinquishment in perpetuity of all present and future rights to
-this software under copyright law.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
-*/
diff --git a/gsk/ngl/stb_rect_pack.h b/gsk/ngl/stb_rect_pack.h
deleted file mode 100644 (file)
index 3ecb1c2..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-// stb_rect_pack.h - v0.99 - public domain - rectangle packing
-// Sean Barrett 2014
-//
-// Useful for e.g. packing rectangular textures into an atlas.
-// Does not do rotation.
-//
-// Not necessarily the awesomest packing method, but better than
-// the totally naive one in stb_truetype (which is primarily what
-// this is meant to replace).
-//
-// Has only had a few tests run, may have issues.
-//
-// More docs to come.
-//
-// No memory allocations; uses qsort() and assert() from stdlib.
-// Can override those by defining STBRP_SORT and STBRP_ASSERT.
-//
-// This library currently uses the Skyline Bottom-Left algorithm.
-//
-// Please note: better rectangle packers are welcome! Please
-// implement them to the same API, but with a different init
-// function.
-//
-// Credits
-//
-//  Library
-//    Sean Barrett
-//  Minor features
-//    Martins Mozeiko
-//    github:IntellectualKitty
-//
-//  Bugfixes / warning fixes
-//    Jeremy Jaussaud
-//
-// Version history:
-//
-//     0.99  (2019-02-07)  warning fixes
-//     0.11  (2017-03-03)  return packing success/fail result
-//     0.10  (2016-10-25)  remove cast-away-const to avoid warnings
-//     0.09  (2016-08-27)  fix compiler warnings
-//     0.08  (2015-09-13)  really fix bug with empty rects (w=0 or h=0)
-//     0.07  (2015-09-13)  fix bug with empty rects (w=0 or h=0)
-//     0.06  (2015-04-15)  added STBRP_SORT to allow replacing qsort
-//     0.05:  added STBRP_ASSERT to allow replacing assert
-//     0.04:  fixed minor bug in STBRP_LARGE_RECTS support
-//     0.01:  initial release
-//
-// LICENSE
-//
-//   See end of file for license information.
-
-//////////////////////////////////////////////////////////////////////////////
-//
-//       INCLUDE SECTION
-//
-
-#ifndef STB_INCLUDE_STB_RECT_PACK_H
-#define STB_INCLUDE_STB_RECT_PACK_H
-
-#define STB_RECT_PACK_VERSION  1
-
-#ifdef STBRP_STATIC
-#define STBRP_DEF static
-#else
-#define STBRP_DEF extern
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct stbrp_context stbrp_context;
-typedef struct stbrp_node    stbrp_node;
-typedef struct stbrp_rect    stbrp_rect;
-
-#ifdef STBRP_LARGE_RECTS
-typedef int            stbrp_coord;
-#else
-typedef unsigned short stbrp_coord;
-#endif
-
-STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
-// Assign packed locations to rectangles. The rectangles are of type
-// 'stbrp_rect' defined below, stored in the array 'rects', and there
-// are 'num_rects' many of them.
-//
-// Rectangles which are successfully packed have the 'was_packed' flag
-// set to a non-zero value and 'x' and 'y' store the minimum location
-// on each axis (i.e. bottom-left in cartesian coordinates, top-left
-// if you imagine y increasing downwards). Rectangles which do not fit
-// have the 'was_packed' flag set to 0.
-//
-// You should not try to access the 'rects' array from another thread
-// while this function is running, as the function temporarily reorders
-// the array while it executes.
-//
-// To pack into another rectangle, you need to call stbrp_init_target
-// again. To continue packing into the same rectangle, you can call
-// this function again. Calling this multiple times with multiple rect
-// arrays will probably produce worse packing results than calling it
-// a single time with the full rectangle array, but the option is
-// available.
-//
-// The function returns 1 if all of the rectangles were successfully
-// packed and 0 otherwise.
-
-struct stbrp_rect
-{
-   // reserved for your use:
-   int            id;
-
-   // input:
-   stbrp_coord    w, h;
-
-   // output:
-   stbrp_coord    x, y;
-   int            was_packed;  // non-zero if valid packing
-
-}; // 16 bytes, nominally
-
-
-STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
-// Initialize a rectangle packer to:
-//    pack a rectangle that is 'width' by 'height' in dimensions
-//    using temporary storage provided by the array 'nodes', which is 'num_nodes' long
-//
-// You must call this function every time you start packing into a new target.
-//
-// There is no "shutdown" function. The 'nodes' memory must stay valid for
-// the following stbrp_pack_rects() call (or calls), but can be freed after
-// the call (or calls) finish.
-//
-// Note: to guarantee best results, either:
-//       1. make sure 'num_nodes' >= 'width'
-//   or  2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
-//
-// If you don't do either of the above things, widths will be quantized to multiples
-// of small integers to guarantee the algorithm doesn't run out of temporary storage.
-//
-// If you do #2, then the non-quantized algorithm will be used, but the algorithm
-// may run out of temporary storage and be unable to pack some rectangles.
-
-STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
-// Optionally call this function after init but before doing any packing to
-// change the handling of the out-of-temp-memory scenario, described above.
-// If you call init again, this will be reset to the default (false).
-
-
-STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
-// Optionally select which packing heuristic the library should use. Different
-// heuristics will produce better/worse results for different data sets.
-// If you call init again, this will be reset to the default.
-
-enum
-{
-   STBRP_HEURISTIC_Skyline_default=0,
-   STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
-   STBRP_HEURISTIC_Skyline_BF_sortHeight
-};
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-// the details of the following structures don't matter to you, but they must
-// be visible so you can handle the memory allocations for them
-
-struct stbrp_node
-{
-   stbrp_coord  x,y;
-   stbrp_node  *next;
-};
-
-struct stbrp_context
-{
-   int width;
-   int height;
-   int align;
-   int init_mode;
-   int heuristic;
-   int num_nodes;
-   stbrp_node *active_head;
-   stbrp_node *free_head;
-   stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
-};
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
-
index 9d9df6628922489c9070a3c3d04c2b987ad1ad6e..3955e57be59b3f1fba0d2d625a5bce3e09f4febe 100644 (file)
@@ -40,7 +40,7 @@
 #include <gtk/gtk.h>
 #define GTK_COMPILATION
 
-#include <gsk/ngl/gsknglrenderer.h>
+#include <gsk/gl/gskglrenderer.h>
 
 #ifdef GDK_WINDOWING_BROADWAY
 #include <gsk/broadway/gskbroadwayrenderer.h>
index 852ecc90ee8c88a0704b202a64fef5e0daa42ae0..4c486c60dcd363682eacbd0624e2b288c279a1fa 100644 (file)
@@ -158,8 +158,6 @@ init_version (GtkInspectorGeneral *gen)
     renderer = "Vulkan";
   else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskGLRenderer") == 0)
     renderer = "GL";
-  else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskNglRenderer") == 0)
-    renderer = "NGL";
   else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskCairoRenderer") == 0)
     renderer = "Cairo";
   else
index 488aec4d6cbbf7aa0926cf4f1c30cbc5fd78c41d..d80d1127c8b60187816683f9ad9e7b42fcae5ab4 100644 (file)
@@ -1,6 +1,6 @@
 #include <gtk/gtk.h>
 
-#include "gsk/ngl/gsknglrenderer.h"
+#include "gsk/gl/gskglrenderer.h"
 
 #define N 20
 
@@ -712,7 +712,7 @@ main (int argc, char *argv[])
   add_test ("/memorytexture/download_float_4x4", test_download_float_4x4);
 
   surface = gdk_surface_new_toplevel (gdk_display_get_default());
-  gl_renderer = gsk_ngl_renderer_new ();
+  gl_renderer = gsk_gl_renderer_new ();
   if (!gsk_renderer_realize (gl_renderer, surface, NULL))
     {
       g_clear_object (&gl_renderer);
index ca970ab3e2611c93f8dfb72bbfc291463d34b579..11598812bf99d0ad7daea859681e0a493a9fe79a 100644 (file)
@@ -1,6 +1,6 @@
 #include <gtk/gtk.h>
 
-#include "gsk/ngl/gsknglrenderer.h"
+#include "gsk/gl/gskglrenderer.h"
 
 /* This function will be called from a thread and/or the main loop.
  * Textures are threadsafe after all. */
@@ -73,7 +73,7 @@ texture_threads (void)
 
   /* 1. Get a GL renderer */
   surface = gdk_surface_new_toplevel (gdk_display_get_default());
-  gl_renderer = gsk_ngl_renderer_new ();
+  gl_renderer = gsk_gl_renderer_new ();
   g_assert_true (gsk_renderer_realize (gl_renderer, surface, NULL));
 
   /* 2. Get a GL texture */
index ba56992f2185a3361021094b0106d4a175bcf6ce..5f74072a84e6b29d7e5cab0cfdabb1b7724bacc5 100644 (file)
@@ -1,6 +1,6 @@
 #include <gtk/gtk.h>
 
-#include "gsk/ngl/fp16private.h"
+#include "gsk/gl/fp16private.h"
 
 static void
 test_constants (void)
index a857646752a50c6890a00da77f6de4baf867ce93..23821da279c49cc9bcca77e8abf5e81a356b7c85 100644 (file)
@@ -94,7 +94,7 @@ informative_render_tests = [
 
 renderers = [
   # name      exclude term
-  [ 'ngl', ''    ],
+  [ 'gl', ''    ],
   [ 'broadway',  '-3d' ],
   [ 'cairo',  '-3d' ],
 ]